mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 13:37:28 +00:00
Enhancements to the process of developing a Marketplace plugin (#5777)
* in dev mode, start watching for changes in all packages * plugin reload service * typo * fixes updates from fs * checks if marketplace dev mode is on to decode the run code from plugin index file * clean up * removes console.log * refactor: marketplace dashboard * prep to merge * dotenv * fixes: install new upadates for one plugin at a time * fixes app crash for new plugins(marketplace/datasource) with default schema * avoid creating docs for marketplace to root docs * Before starting watcher, build the marketplace once. * fixes: installed plugin crashes if deleting the entire plugin from the dir, but the build still haves the plugin files
This commit is contained in:
parent
70ffc1e27b
commit
7dea6c9ad1
20 changed files with 335 additions and 180 deletions
|
|
@ -74,6 +74,21 @@ export default class Create extends Command {
|
|||
|
||||
let repoUrl;
|
||||
|
||||
const commonHygenArgs = [
|
||||
'plugin',
|
||||
'new',
|
||||
'--name',
|
||||
`${args.plugin_name}`,
|
||||
'--type',
|
||||
`${type}`,
|
||||
'--display_name',
|
||||
`${name}`,
|
||||
'--plugins_path',
|
||||
`${pluginsPath}`,
|
||||
];
|
||||
|
||||
const hygenArgs = !marketplace ? [...commonHygenArgs, '--docs_path', `${docsPath}`] : commonHygenArgs;
|
||||
|
||||
if (marketplace) {
|
||||
const buffer = fs.readFileSync(path.join('server', 'src', 'assets', 'marketplace', 'plugins.json'), 'utf8');
|
||||
const pluginsJson = JSON.parse(buffer);
|
||||
|
|
@ -89,21 +104,6 @@ export default class Create extends Command {
|
|||
});
|
||||
}
|
||||
|
||||
const hygenArgs = [
|
||||
'plugin',
|
||||
'new',
|
||||
'--name',
|
||||
`${args.plugin_name}`,
|
||||
'--type',
|
||||
`${type}`,
|
||||
'--display_name',
|
||||
`${name}`,
|
||||
'--plugins_path',
|
||||
`${pluginsPath}`,
|
||||
'--docs_path',
|
||||
`${docsPath}`,
|
||||
];
|
||||
|
||||
CliUx.ux.action.start('creating plugin');
|
||||
|
||||
await runner(hygenArgs, {
|
||||
|
|
|
|||
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
|
|
@ -28,6 +28,7 @@
|
|||
"date-fns": "^2.29.3",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
"dompurify": "^3.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"draft-js": "^0.11.7",
|
||||
"draft-js-export-html": "^1.4.1",
|
||||
"driver.js": "^0.9.8",
|
||||
|
|
@ -31098,6 +31099,14 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/draft-js": {
|
||||
"version": "0.11.7",
|
||||
"license": "MIT",
|
||||
|
|
@ -68445,6 +68454,11 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
|
||||
},
|
||||
"draft-js": {
|
||||
"version": "0.11.7",
|
||||
"requires": {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"date-fns": "^2.29.3",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
"dompurify": "^3.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"draft-js": "^0.11.7",
|
||||
"draft-js-export-html": "^1.4.1",
|
||||
"driver.js": "^0.9.8",
|
||||
|
|
|
|||
|
|
@ -1,41 +1,53 @@
|
|||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import { marketplaceService, pluginsService } from '@/_services';
|
||||
import { pluginsService } from '@/_services';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import Spinner from '@/_ui/Spinner';
|
||||
import { capitalizeFirstLetter } from './utils';
|
||||
|
||||
export const InstalledPlugins = ({ isActive }) => {
|
||||
const [plugins, setPlugins] = React.useState([]);
|
||||
export const InstalledPlugins = ({
|
||||
allPlugins = [],
|
||||
installedPlugins,
|
||||
fetching,
|
||||
fetchPlugins,
|
||||
ENABLE_MARKETPLACE_DEV_MODE,
|
||||
}) => {
|
||||
return (
|
||||
<div className="col-9">
|
||||
{fetching && (
|
||||
<div className="m-auto text-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
{!fetching && allPlugins.length > 0 && (
|
||||
<div className="row row-cards">
|
||||
{installedPlugins?.map((plugin) => {
|
||||
const marketplacePlugin = allPlugins?.find((m) => m.id === plugin.pluginId);
|
||||
const isUpdateAvailable = marketplacePlugin?.version !== plugin.version;
|
||||
return (
|
||||
<InstalledPlugins.Plugin
|
||||
key={plugin.id}
|
||||
plugin={plugin}
|
||||
marketplacePlugin={marketplacePlugin}
|
||||
fetchPlugins={fetchPlugins}
|
||||
isDevMode={ENABLE_MARKETPLACE_DEV_MODE}
|
||||
isUpdateAvailable={isUpdateAvailable}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{!fetching && installedPlugins?.length === 0 && (
|
||||
<div className="empty">
|
||||
<p className="empty-title">No results found</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InstalledPluginCard = ({ plugin, marketplacePlugin, fetchPlugins, isDevMode, isUpdateAvailable }) => {
|
||||
const [updating, setUpdating] = React.useState(false);
|
||||
const [marketplacePlugins, setMarketplacePlugins] = React.useState([]);
|
||||
const [fetching, setFetching] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
marketplaceService
|
||||
.findAll()
|
||||
.then(({ data = [] }) => setMarketplacePlugins(data))
|
||||
.catch((error) => {
|
||||
toast.error(error?.message || 'something went wrong');
|
||||
});
|
||||
}, [isActive]);
|
||||
|
||||
const fetchPlugins = async () => {
|
||||
setFetching(true);
|
||||
const { data, error } = await pluginsService.findAll();
|
||||
setFetching(false);
|
||||
|
||||
if (error) {
|
||||
toast.error(error?.message || 'something went wrong');
|
||||
return;
|
||||
}
|
||||
|
||||
setPlugins(data);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchPlugins();
|
||||
}, [isActive]);
|
||||
|
||||
const deletePlugin = async ({ id, name }) => {
|
||||
var result = confirm('Are you sure you want to delete ' + name + '?');
|
||||
|
|
@ -70,77 +82,97 @@ export const InstalledPlugins = ({ isActive }) => {
|
|||
fetchPlugins();
|
||||
};
|
||||
|
||||
const reloadPlugin = async ({ id, name }) => {
|
||||
setUpdating(true);
|
||||
const { error } = await pluginsService.reloadPlugin(id);
|
||||
setUpdating(false);
|
||||
|
||||
if (error) {
|
||||
toast.error(error?.message || `Unable to reload ${name}`);
|
||||
return;
|
||||
}
|
||||
toast.success(`${name} reloaded`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="col-9">
|
||||
{fetching && (
|
||||
<div className="m-auto text-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
{!fetching && (
|
||||
<div className="row row-cards">
|
||||
{plugins?.map((plugin) => {
|
||||
const marketplacePlugin = marketplacePlugins.find((m) => m.id === plugin.pluginId);
|
||||
const isUpdateAvailable = marketplacePlugin?.version !== plugin.version;
|
||||
return (
|
||||
<div key={plugin.id} className="col-sm-6 col-lg-4">
|
||||
<div className="card card-sm card-borderless">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="text-white avatar">
|
||||
<img height="32" width="32" src={`data:image/svg+xml;base64,${plugin.iconFile.data}`} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium text-capitalize">{plugin.name}</div>
|
||||
<div>{plugin.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<sub>
|
||||
v{plugin.version}{' '}
|
||||
{isUpdateAvailable && (
|
||||
<span
|
||||
className={cx('link-span', { disabled: updating })}
|
||||
onClick={() => updatePlugin(plugin, marketplacePlugin.version)}
|
||||
>
|
||||
<small className="font-weight-light">
|
||||
(click to update to v{marketplacePlugin.version})
|
||||
</small>
|
||||
</span>
|
||||
)}
|
||||
</sub>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<div
|
||||
className={cx('cursor-pointer link-primary', { disabled: updating })}
|
||||
onClick={() => deletePlugin(plugin)}
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{updating && (
|
||||
<div className="progress progress-sm">
|
||||
<div className="progress-bar progress-bar-indeterminate"></div>
|
||||
</div>
|
||||
<div key={plugin.id} className="col-sm-6 col-lg-4">
|
||||
<div className="card card-sm card-borderless">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="text-white avatar">
|
||||
<img height="32" width="32" src={`data:image/svg+xml;base64,${plugin.iconFile.data}`} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium text-capitalize">{plugin.name}</div>
|
||||
<div>{plugin.description}</div>
|
||||
</div>
|
||||
<div className="col-2">
|
||||
{isDevMode && (
|
||||
<button
|
||||
disabled={updating}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
reloadPlugin(plugin);
|
||||
}}
|
||||
className="btn btn-icon"
|
||||
aria-label="Button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="icon icon-tabler icon-tabler-refresh"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4"></path>
|
||||
<path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<sub>
|
||||
v{plugin.version}{' '}
|
||||
{isUpdateAvailable && (
|
||||
<span
|
||||
className={cx('link-span', { disabled: updating })}
|
||||
onClick={() => updatePlugin(plugin, marketplacePlugin?.version)}
|
||||
>
|
||||
<small className="font-weight-light">(click to update to v{marketplacePlugin?.version})</small>
|
||||
</span>
|
||||
)}
|
||||
</sub>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<div
|
||||
className={cx('cursor-pointer link-primary', { disabled: updating })}
|
||||
onClick={() => deletePlugin(plugin)}
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!fetching && plugins?.length === 0 && (
|
||||
<div className="empty">
|
||||
<p className="empty-title">No results found</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{updating && (
|
||||
<div className="progress progress-sm">
|
||||
<div className="progress-bar progress-bar-indeterminate"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InstalledPlugins.Plugin = InstalledPluginCard;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
import React from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { MarketplaceCard } from './MarketplaceCard';
|
||||
import { marketplaceService, pluginsService } from '@/_services';
|
||||
import { pluginsService } from '@/_services';
|
||||
|
||||
export const MarketplacePlugins = ({ isActive }) => {
|
||||
const [plugins, setPlugins] = React.useState([]);
|
||||
export const MarketplacePlugins = ({ allPlugins = [] }) => {
|
||||
const [installedPlugins, setInstalledPlugins] = React.useState({});
|
||||
React.useEffect(() => {
|
||||
marketplaceService
|
||||
.findAll()
|
||||
.then(({ data = [] }) => setPlugins(data))
|
||||
.catch((error) => {
|
||||
toast.error(error?.message || 'something went wrong');
|
||||
});
|
||||
}, [isActive]);
|
||||
|
||||
React.useEffect(() => {
|
||||
pluginsService
|
||||
|
|
@ -28,12 +19,16 @@ export const MarketplacePlugins = ({ isActive }) => {
|
|||
.catch((error) => {
|
||||
toast.error(error?.message || 'something went wrong');
|
||||
});
|
||||
|
||||
return () => {
|
||||
setInstalledPlugins({});
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="col-9">
|
||||
<div className="row row-cards">
|
||||
{plugins?.map(({ id, name, repo, version, description }) => {
|
||||
{allPlugins?.map(({ id, name, repo, version, description }) => {
|
||||
return (
|
||||
<MarketplaceCard
|
||||
key={id}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,46 @@ import Layout from '@/_ui/Layout';
|
|||
import { ListGroupItem } from './ListGroupItem';
|
||||
import { InstalledPlugins } from './InstalledPlugins';
|
||||
import { MarketplacePlugins } from './MarketplacePlugins';
|
||||
import { marketplaceService, pluginsService } from '@/_services';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import config from 'config';
|
||||
|
||||
const MarketplacePage = ({ darkMode, switchDarkMode }) => {
|
||||
const [active, setActive] = React.useState('installed');
|
||||
const [marketplacePlugins, setMarketplacePlugins] = React.useState([]);
|
||||
const [installedPlugins, setInstalledPlugins] = React.useState([]);
|
||||
const [fetchingInstalledPlugins, setFetching] = React.useState(false);
|
||||
|
||||
const ENABLE_MARKETPLACE_DEV_MODE = config.ENABLE_MARKETPLACE_DEV_MODE === 'true';
|
||||
|
||||
React.useEffect(() => {
|
||||
marketplaceService
|
||||
.findAll()
|
||||
.then(({ data = [] }) => setMarketplacePlugins(data))
|
||||
.catch((error) => {
|
||||
toast.error(error?.message || 'something went wrong');
|
||||
});
|
||||
|
||||
fetchPlugins();
|
||||
|
||||
() => {
|
||||
setMarketplacePlugins([]);
|
||||
setInstalledPlugins([]);
|
||||
};
|
||||
}, [active]);
|
||||
|
||||
const fetchPlugins = async () => {
|
||||
setFetching(true);
|
||||
const { data, error } = await pluginsService.findAll();
|
||||
setFetching(false);
|
||||
|
||||
if (error) {
|
||||
toast.error(error?.message || 'something went wrong');
|
||||
return;
|
||||
}
|
||||
|
||||
setInstalledPlugins(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout switchDarkMode={switchDarkMode} darkMode={darkMode}>
|
||||
|
|
@ -30,9 +67,15 @@ const MarketplacePage = ({ darkMode, switchDarkMode }) => {
|
|||
</div>
|
||||
</div>
|
||||
{active === 'installed' ? (
|
||||
<InstalledPlugins isActive={active === 'installed'} darkMode={darkMode} />
|
||||
<InstalledPlugins
|
||||
allPlugins={marketplacePlugins}
|
||||
installedPlugins={installedPlugins}
|
||||
fetching={fetchingInstalledPlugins}
|
||||
fetchPlugins={fetchPlugins}
|
||||
ENABLE_MARKETPLACE_DEV_MODE={ENABLE_MARKETPLACE_DEV_MODE}
|
||||
/>
|
||||
) : (
|
||||
<MarketplacePlugins isActive={active === 'marketplace'} darkMode={darkMode} />
|
||||
<MarketplacePlugins allPlugins={marketplacePlugins} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -40,29 +40,30 @@ const DynamicForm = ({
|
|||
|
||||
React.useEffect(() => {
|
||||
const { properties } = schema;
|
||||
if (isEmpty(properties)) return null;
|
||||
if (!isEmpty(properties)) {
|
||||
let fields = {};
|
||||
let encrpytedFieldsProps = {};
|
||||
const flipComponentDropdown = find(properties, ['type', 'dropdown-component-flip']);
|
||||
|
||||
let fields = {};
|
||||
let encrpytedFieldsProps = {};
|
||||
const flipComponentDropdown = find(properties, ['type', 'dropdown-component-flip']);
|
||||
if (flipComponentDropdown) {
|
||||
const selector = options?.[flipComponentDropdown?.key]?.value;
|
||||
fields = { ...flipComponentDropdown?.commonFields, ...properties[selector] };
|
||||
} else {
|
||||
fields = { ...properties };
|
||||
}
|
||||
|
||||
if (flipComponentDropdown) {
|
||||
const selector = options?.[flipComponentDropdown?.key]?.value;
|
||||
fields = { ...flipComponentDropdown?.commonFields, ...properties[selector] };
|
||||
} else {
|
||||
fields = { ...properties };
|
||||
Object.keys(fields).map((key) => {
|
||||
const { type, encrypted } = fields[key];
|
||||
if ((type === 'password' || encrypted) && !(key in computedProps)) {
|
||||
//Editable encrypted fields only if datasource doesn't exists
|
||||
encrpytedFieldsProps[key] = {
|
||||
disabled: !!selectedDataSource?.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
setComputedProps({ ...computedProps, ...encrpytedFieldsProps });
|
||||
}
|
||||
|
||||
Object.keys(fields).map((key) => {
|
||||
const { type, encrypted } = fields[key];
|
||||
if ((type === 'password' || encrypted) && !(key in computedProps)) {
|
||||
//Editable encrypted fields only if datasource doesn't exists
|
||||
encrpytedFieldsProps[key] = {
|
||||
disabled: !!selectedDataSource?.id,
|
||||
};
|
||||
}
|
||||
});
|
||||
setComputedProps({ ...computedProps, ...encrpytedFieldsProps });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [options]);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,14 @@ function deletePlugin(id) {
|
|||
return adapter.delete(`/plugins/${id}`);
|
||||
}
|
||||
|
||||
function reloadPlugin(id) {
|
||||
return adapter.post(`/plugins/${id}/reload`);
|
||||
}
|
||||
|
||||
export const pluginsService = {
|
||||
findAll,
|
||||
installPlugin,
|
||||
updatePlugin,
|
||||
deletePlugin,
|
||||
reloadPlugin,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const webpack = require('webpack');
|
|||
const path = require('path');
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
require('dotenv').config({ path: '../.env' });
|
||||
const hash = require('string-hash');
|
||||
|
||||
const environment = process.env.NODE_ENV === 'production' ? 'production' : 'development';
|
||||
|
|
@ -169,6 +170,7 @@ module.exports = {
|
|||
COMMENT_FEATURE_ENABLE: process.env.COMMENT_FEATURE_ENABLE ?? true,
|
||||
ENABLE_TOOLJET_DB: process.env.ENABLE_TOOLJET_DB ?? true,
|
||||
ENABLE_MULTIPLAYER_EDITING: true,
|
||||
ENABLE_MARKETPLACE_DEV_MODE: process.env.ENABLE_MARKETPLACE_DEV_MODE,
|
||||
TOOLJET_MARKETPLACE_URL:
|
||||
process.env.TOOLJET_MARKETPLACE_URL || 'https://tooljet-plugins-production.s3.us-east-2.amazonaws.com',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
to: <%= docs_path %>/docs/data-sources/<%= name %>.md
|
||||
---
|
||||
<%
|
||||
Display_name = h.capitalize(display_name)
|
||||
%>
|
||||
# <%= name %>
|
||||
|
||||
ToolJet can connect to <%= Display_name %> datasource to read and write data.
|
||||
|
||||
- [Connection](#connection)
|
||||
- [Getting Started](#querying-<%= name %>)
|
||||
|
||||
## Connection
|
||||
|
||||
## Querying <%= Display_name %> operations
|
||||
|
|
@ -15,7 +15,8 @@ to: <%= plugins_path %>/plugins/<%= name %>/package.json
|
|||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"build": "ncc build lib/index.ts -o dist"
|
||||
"build": "ncc build lib/index.ts -o dist",
|
||||
"watch": "ncc build lib/index.ts -o dist --watch"
|
||||
},
|
||||
"homepage": "https://github.com/tooljet/tooljet#readme",
|
||||
"dependencies": {
|
||||
|
|
|
|||
5
marketplace/lerna.json
Normal file
5
marketplace/lerna.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.0.0"
|
||||
}
|
||||
|
|
@ -7,7 +7,13 @@
|
|||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"aws-sdk": "^2.1326.0",
|
||||
"lerna": "^6.4.1",
|
||||
"mime-types": "^2.1.35",
|
||||
"recursive-readdir": "^2.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start:watch": "lerna run watch --stream --parallel",
|
||||
"build": "npm run build --workspaces",
|
||||
"start:dev": "npm run build && npm run start:watch"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,9 @@ export default class Github implements QueryService {
|
|||
|
||||
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise<QueryResult> {
|
||||
const operation: Operation = queryOptions.operation;
|
||||
console.log('---octakit operation', {
|
||||
queryOptions
|
||||
|
||||
});
|
||||
|
||||
const octokit:Octokit = await this.getConnection(sourceOptions);
|
||||
let result = {};
|
||||
|
||||
|
||||
try {
|
||||
switch (operation) {
|
||||
case Operation.GetUserInfo:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"build": "ncc build lib/index.ts -o dist"
|
||||
"build": "ncc build lib/index.ts -o dist",
|
||||
"watch": "ncc build lib/index.ts -o dist --watch"
|
||||
},
|
||||
"homepage": "https://github.com/tooljet/tooljet#readme",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"build": "ncc build lib/index.ts -o dist"
|
||||
"build": "ncc build lib/index.ts -o dist",
|
||||
"watch": "ncc build lib/index.ts -o dist --watch"
|
||||
},
|
||||
"homepage": "https://github.com/tooljet/tooljet#readme",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"build": "ncc build lib/index.ts -o dist"
|
||||
"build": "ncc build lib/index.ts -o dist",
|
||||
"watch": "ncc build lib/index.ts -o dist --watch"
|
||||
},
|
||||
"homepage": "https://github.com/tooljet/tooljet#readme",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -77,4 +77,10 @@ export class PluginsController {
|
|||
|
||||
return this.pluginsService.remove(id);
|
||||
}
|
||||
|
||||
@Post(':id/reload')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async reload(@Param('id') id: string) {
|
||||
return this.pluginsService.reload(id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,13 @@ export class PluginsHelper {
|
|||
}
|
||||
|
||||
async getService(pluginId: string, kind: string) {
|
||||
const isMarketPlaceDev = process.env.ENABLE_MARKETPLACE_DEV_MODE === 'true';
|
||||
|
||||
try {
|
||||
if (pluginId) {
|
||||
let decoded: string;
|
||||
if (this.plugins[pluginId]) {
|
||||
|
||||
if (!isMarketPlaceDev && this.plugins[pluginId]) {
|
||||
decoded = this.plugins[pluginId];
|
||||
} else {
|
||||
const plugin = await this.pluginsRepository.findOne({ where: { id: pluginId }, relations: ['indexFile'] });
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { dbTransactionWrap } from 'src/helpers/utils.helper';
|
|||
import { UpdateFileDto } from '@dto/update-file.dto';
|
||||
|
||||
const jszipInstance = new jszip();
|
||||
const fs = require('fs');
|
||||
|
||||
@Injectable()
|
||||
export class PluginsService {
|
||||
|
|
@ -194,23 +195,39 @@ export class PluginsService {
|
|||
return [indexFile, operationsFile, iconFile, manifestFile];
|
||||
}
|
||||
|
||||
const fs = require('fs/promises');
|
||||
async function readFile(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const readStream = fs.createReadStream(filePath, { encoding: 'utf8' });
|
||||
let fileContent = '';
|
||||
|
||||
readStream.on('data', (chunk) => {
|
||||
fileContent += chunk;
|
||||
});
|
||||
|
||||
readStream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
readStream.on('end', () => {
|
||||
resolve(fileContent);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: big files are going to have a major impact on the memory consumption and speed of execution of the program
|
||||
// as `fs.readFile` read the full content of the file in memory before returning the data.
|
||||
// In this case, a better option is to read the file content using streams.
|
||||
const [indexFile, operationsFile, iconFile, manifestFile] = await Promise.all([
|
||||
fs.readFile(`../marketplace/plugins/${id}/dist/index.js`, 'utf8'),
|
||||
fs.readFile(`../marketplace/plugins/${id}/lib/operations.json`, 'utf8'),
|
||||
fs.readFile(`../marketplace/plugins/${id}/lib/icon.svg`, 'utf8'),
|
||||
fs.readFile(`../marketplace/plugins/${id}/lib/manifest.json`, 'utf8'),
|
||||
readFile(`../marketplace/plugins/${id}/dist/index.js`),
|
||||
readFile(`../marketplace/plugins/${id}/lib/operations.json`),
|
||||
readFile(`../marketplace/plugins/${id}/lib/icon.svg`),
|
||||
readFile(`../marketplace/plugins/${id}/lib/manifest.json`),
|
||||
]);
|
||||
|
||||
return [indexFile, operationsFile, iconFile, manifestFile];
|
||||
}
|
||||
|
||||
fetchPluginFiles(id: string, repo: string) {
|
||||
if (repo) return this.fetchPluginFilesFromRepo(repo);
|
||||
if (repo && repo.length > 0) {
|
||||
return this.fetchPluginFilesFromRepo(repo);
|
||||
}
|
||||
|
||||
return this.fetchPluginFilesFromS3(id);
|
||||
}
|
||||
|
|
@ -230,4 +247,47 @@ export class PluginsService {
|
|||
async remove(id: string) {
|
||||
return await this.pluginsRepository.delete(id);
|
||||
}
|
||||
|
||||
async reload(id: string) {
|
||||
const queryRunner = this.connection.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const plugin = await this.findOne(id);
|
||||
const { pluginId, repo, version } = plugin;
|
||||
|
||||
const [index, operations, icon, manifest] = await this.fetchPluginFiles(pluginId, repo);
|
||||
|
||||
const files = { index, operations, icon, manifest };
|
||||
|
||||
const uploadedFiles: { index?: File; operations?: File; icon?: File; manifest?: File } = {};
|
||||
await Promise.all(
|
||||
Object.keys(files).map(async (key) => {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const file = files[key];
|
||||
const fileDto = new UpdateFileDto();
|
||||
fileDto.data = encode(file);
|
||||
fileDto.filename = key;
|
||||
uploadedFiles[key] = await this.filesService.update(plugin[`${key}FileId`], fileDto, manager);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const updatedPlugin = new Plugin();
|
||||
|
||||
updatedPlugin.id = plugin.id;
|
||||
updatedPlugin.repo = repo || '';
|
||||
updatedPlugin.version = version;
|
||||
|
||||
return this.pluginsRepository.save(updatedPlugin);
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw new InternalServerErrorException(error);
|
||||
} finally {
|
||||
await queryRunner.commitTransaction();
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue