@budibase/server 1.3.15-alpha.0 → 1.3.15-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builder/assets/{index.f5c54dd2.css → index.ecdac070.css} +2 -2
- package/builder/assets/{index.b266d29f.js → index.ed9ae6a1.js} +288 -288
- package/builder/index.html +2 -2
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
- package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +1 -1
- package/coverage/lcov-report/api/controllers/datasource.js.html +1 -1
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +1 -1
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/migrations.js.html +1 -1
- package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
- package/coverage/lcov-report/api/controllers/query/index.html +1 -1
- package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/role.js.html +1 -1
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/index.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
- package/coverage/lcov-report/api/controllers/script.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
- package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/migrations.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +1 -1
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/utils/index.html +1 -1
- package/coverage/lcov-report/api/routes/utils/validators.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +1 -1
- package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
- package/coverage/lcov-report/automations/bullboard.js.html +1 -1
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +1 -1
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +1 -1
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/loop.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
- package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/triggers.js.html +1 -1
- package/coverage/lcov-report/constants/index.html +1 -1
- package/coverage/lcov-report/constants/index.js.html +1 -1
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
- package/coverage/lcov-report/db/index.html +1 -1
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
- package/coverage/lcov-report/db/newid.js.html +1 -1
- package/coverage/lcov-report/db/utils.js.html +1 -1
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +1 -1
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/builder.js.html +1 -1
- package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
- package/coverage/lcov-report/middleware/index.html +1 -1
- package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
- package/coverage/lcov-report/middleware/publicApi.js.html +1 -1
- package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
- package/coverage/lcov-report/utilities/global.js.html +1 -1
- package/coverage/lcov-report/utilities/index.html +1 -1
- package/coverage/lcov-report/utilities/index.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
- package/coverage/lcov-report/utilities/security.js.html +1 -1
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
- package/dist/api/controllers/plugin/file.js +24 -0
- package/dist/api/controllers/plugin/github.js +69 -0
- package/dist/api/controllers/{plugin.js → plugin/index.js} +83 -13
- package/dist/api/controllers/plugin/npm.js +58 -0
- package/dist/api/controllers/plugin/uploaders.js +11 -0
- package/dist/api/controllers/plugin/url.js +24 -0
- package/dist/api/controllers/plugin/utils.js +26 -0
- package/dist/api/routes/plugin.js +1 -0
- package/dist/package.json +6 -6
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utilities/fileSystem/index.js +56 -12
- package/package.json +7 -7
- package/src/api/controllers/plugin/file.ts +15 -0
- package/src/api/controllers/plugin/github.ts +75 -0
- package/src/api/controllers/plugin/index.ts +202 -0
- package/src/api/controllers/plugin/npm.ts +56 -0
- package/src/api/controllers/plugin/uploaders.ts +4 -0
- package/src/api/controllers/plugin/url.ts +12 -0
- package/src/api/controllers/plugin/utils.ts +19 -0
- package/src/api/routes/plugin.ts +1 -0
- package/src/module.d.ts +1 -0
- package/src/utilities/fileSystem/index.js +72 -12
- package/src/api/controllers/plugin.ts +0 -110
|
@@ -14,7 +14,7 @@ const { join } = require("path");
|
|
|
14
14
|
const uuid = require("uuid/v4");
|
|
15
15
|
const { doWithDB, dangerousGetDB, closeDB, } = require("@budibase/backend-core/db");
|
|
16
16
|
const { ObjectStoreBuckets } = require("../../constants");
|
|
17
|
-
const { upload, retrieve, retrieveToTmp, streamUpload, deleteFolder, downloadTarball, deleteFiles, } = require("./utilities");
|
|
17
|
+
const { upload, retrieve, retrieveToTmp, streamUpload, deleteFolder, downloadTarball, downloadTarballDirect, deleteFiles, } = require("./utilities");
|
|
18
18
|
const { updateClientLibrary } = require("./clientLibrary");
|
|
19
19
|
const { checkSlashesInUrl } = require("../");
|
|
20
20
|
const env = require("../../environment");
|
|
@@ -295,32 +295,47 @@ exports.cleanup = appIds => {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
const createTempFolder = item => {
|
|
299
|
+
const path = join(budibaseTempDir(), item);
|
|
300
|
+
try {
|
|
301
|
+
// remove old tmp directories automatically - don't combine
|
|
302
|
+
if (fs.existsSync(path)) {
|
|
303
|
+
fs.rmSync(path, { recursive: true, force: true });
|
|
304
|
+
}
|
|
305
|
+
fs.mkdirSync(path);
|
|
301
306
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (fs.existsSync(path)) {
|
|
305
|
-
fs.rmSync(path, { recursive: true, force: true });
|
|
307
|
+
catch (err) {
|
|
308
|
+
throw new Error(`Path cannot be created: ${err.message}`);
|
|
306
309
|
}
|
|
307
|
-
|
|
310
|
+
return path;
|
|
311
|
+
};
|
|
312
|
+
exports.createTempFolder = createTempFolder;
|
|
313
|
+
const extractTarball = (fromFilePath, toPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
308
314
|
yield tar.extract({
|
|
309
|
-
file:
|
|
310
|
-
C:
|
|
315
|
+
file: fromFilePath,
|
|
316
|
+
C: toPath,
|
|
311
317
|
});
|
|
318
|
+
});
|
|
319
|
+
exports.extractTarball = extractTarball;
|
|
320
|
+
const getPluginMetadata = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
312
321
|
let metadata = {};
|
|
313
322
|
try {
|
|
314
323
|
const pkg = fs.readFileSync(join(path, "package.json"), "utf8");
|
|
315
324
|
const schema = fs.readFileSync(join(path, "schema.json"), "utf8");
|
|
316
325
|
metadata.schema = JSON.parse(schema);
|
|
317
326
|
metadata.package = JSON.parse(pkg);
|
|
327
|
+
if (!metadata.package.name ||
|
|
328
|
+
!metadata.package.version ||
|
|
329
|
+
!metadata.package.description) {
|
|
330
|
+
throw new Error("package.json is missing one of 'name', 'version' or 'description'.");
|
|
331
|
+
}
|
|
318
332
|
}
|
|
319
333
|
catch (err) {
|
|
320
|
-
throw new Error(
|
|
334
|
+
throw new Error(`Unable to process schema.json/package.json in plugin. ${err.message}`);
|
|
321
335
|
}
|
|
322
336
|
return { metadata, directory: path };
|
|
323
337
|
});
|
|
338
|
+
exports.getPluginMetadata = getPluginMetadata;
|
|
324
339
|
exports.getDatasourcePlugin = (name, url, hash) => __awaiter(void 0, void 0, void 0, function* () {
|
|
325
340
|
if (!fs.existsSync(DATASOURCE_PATH)) {
|
|
326
341
|
fs.mkdirSync(DATASOURCE_PATH);
|
|
@@ -350,6 +365,34 @@ exports.getDatasourcePlugin = (name, url, hash) => __awaiter(void 0, void 0, voi
|
|
|
350
365
|
throw new Error(`Unable to retrieve plugin - reason: ${yield response.text()}`);
|
|
351
366
|
}
|
|
352
367
|
});
|
|
368
|
+
/**
|
|
369
|
+
* Find for a file recursively from start path applying filter, return first match
|
|
370
|
+
*/
|
|
371
|
+
exports.findFileRec = (startPath, filter) => {
|
|
372
|
+
if (!fs.existsSync(startPath)) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const files = fs.readdirSync(startPath);
|
|
376
|
+
for (let i = 0, len = files.length; i < len; i++) {
|
|
377
|
+
const filename = join(startPath, files[i]);
|
|
378
|
+
const stat = fs.lstatSync(filename);
|
|
379
|
+
if (stat.isDirectory()) {
|
|
380
|
+
return exports.findFileRec(filename, filter);
|
|
381
|
+
}
|
|
382
|
+
else if (filename.endsWith(filter)) {
|
|
383
|
+
return filename;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* Remove a folder which is not empty from the file system
|
|
389
|
+
*/
|
|
390
|
+
exports.deleteFolderFileSystem = path => {
|
|
391
|
+
if (!fs.existsSync(path)) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
fs.rmSync(path, { recursive: true, force: true });
|
|
395
|
+
};
|
|
353
396
|
/**
|
|
354
397
|
* Full function definition for below can be found in the utilities.
|
|
355
398
|
*/
|
|
@@ -357,5 +400,6 @@ exports.upload = upload;
|
|
|
357
400
|
exports.retrieve = retrieve;
|
|
358
401
|
exports.retrieveToTmp = retrieveToTmp;
|
|
359
402
|
exports.deleteFiles = deleteFiles;
|
|
403
|
+
exports.downloadTarballDirect = downloadTarballDirect;
|
|
360
404
|
exports.TOP_LEVEL_PATH = TOP_LEVEL_PATH;
|
|
361
405
|
exports.NODE_MODULES_PATH = NODE_MODULES_PATH;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "1.3.15-alpha.
|
|
4
|
+
"version": "1.3.15-alpha.1",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
"license": "GPL-3.0",
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
80
|
-
"@budibase/backend-core": "1.3.15-alpha.
|
|
81
|
-
"@budibase/client": "1.3.15-alpha.
|
|
82
|
-
"@budibase/pro": "1.3.
|
|
83
|
-
"@budibase/string-templates": "1.3.15-alpha.
|
|
84
|
-
"@budibase/types": "1.3.15-alpha.
|
|
80
|
+
"@budibase/backend-core": "1.3.15-alpha.1",
|
|
81
|
+
"@budibase/client": "1.3.15-alpha.1",
|
|
82
|
+
"@budibase/pro": "1.3.15-alpha.0",
|
|
83
|
+
"@budibase/string-templates": "1.3.15-alpha.1",
|
|
84
|
+
"@budibase/types": "1.3.15-alpha.1",
|
|
85
85
|
"@bull-board/api": "3.7.0",
|
|
86
86
|
"@bull-board/koa": "3.9.4",
|
|
87
87
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -198,5 +198,5 @@
|
|
|
198
198
|
"optionalDependencies": {
|
|
199
199
|
"oracledb": "5.3.0"
|
|
200
200
|
},
|
|
201
|
-
"gitHead": "
|
|
201
|
+
"gitHead": "a92a8ed882cde62db5903d495112e202173defc7"
|
|
202
202
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTempFolder,
|
|
3
|
+
getPluginMetadata,
|
|
4
|
+
extractTarball,
|
|
5
|
+
} from "../../../utilities/fileSystem"
|
|
6
|
+
|
|
7
|
+
export async function fileUpload(file: { name: string; path: string }) {
|
|
8
|
+
if (!file.name.endsWith(".tar.gz")) {
|
|
9
|
+
throw new Error("Plugin must be compressed into a gzipped tarball.")
|
|
10
|
+
}
|
|
11
|
+
const path = createTempFolder(file.name.split(".tar.gz")[0])
|
|
12
|
+
await extractTarball(file.path, path)
|
|
13
|
+
|
|
14
|
+
return await getPluginMetadata(path)
|
|
15
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getPluginMetadata } from "../../../utilities/fileSystem"
|
|
2
|
+
import fetch from "node-fetch"
|
|
3
|
+
import { downloadUnzipTarball } from "./utils"
|
|
4
|
+
|
|
5
|
+
export async function request(
|
|
6
|
+
url: string,
|
|
7
|
+
headers: { [key: string]: string },
|
|
8
|
+
err: string
|
|
9
|
+
) {
|
|
10
|
+
const response = await fetch(url, { headers })
|
|
11
|
+
if (response.status >= 300) {
|
|
12
|
+
const respErr = await response.text()
|
|
13
|
+
throw new Error(`Error: ${err} - ${respErr}`)
|
|
14
|
+
}
|
|
15
|
+
return response.json()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function githubUpload(url: string, name = "", token = "") {
|
|
19
|
+
let githubUrl = url
|
|
20
|
+
|
|
21
|
+
if (!githubUrl.includes("https://github.com/")) {
|
|
22
|
+
throw new Error("The plugin origin must be from Github")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (url.includes(".git")) {
|
|
26
|
+
githubUrl = url.replace(".git", "")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const githubApiUrl = githubUrl.replace(
|
|
30
|
+
"https://github.com/",
|
|
31
|
+
"https://api.github.com/repos/"
|
|
32
|
+
)
|
|
33
|
+
const headers: any = token ? { Authorization: `Bearer ${token}` } : {}
|
|
34
|
+
const pluginDetails = await request(
|
|
35
|
+
githubApiUrl,
|
|
36
|
+
headers,
|
|
37
|
+
"Repository not found"
|
|
38
|
+
)
|
|
39
|
+
const pluginName = pluginDetails.name || name
|
|
40
|
+
const pluginLatestReleaseUrl = pluginDetails?.["releases_url"]
|
|
41
|
+
? pluginDetails?.["releases_url"].replace("{/id}", "/latest")
|
|
42
|
+
: undefined
|
|
43
|
+
if (!pluginLatestReleaseUrl) {
|
|
44
|
+
throw new Error("Github release not found")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const pluginReleaseDetails = await request(
|
|
48
|
+
pluginLatestReleaseUrl,
|
|
49
|
+
headers,
|
|
50
|
+
"Github latest release not found"
|
|
51
|
+
)
|
|
52
|
+
const pluginReleaseTarballAsset = pluginReleaseDetails?.assets?.find(
|
|
53
|
+
(x: any) => x?.["content_type"] === "application/gzip"
|
|
54
|
+
)
|
|
55
|
+
const pluginLastReleaseTarballUrl =
|
|
56
|
+
pluginReleaseTarballAsset?.["browser_download_url"]
|
|
57
|
+
if (!pluginLastReleaseTarballUrl) {
|
|
58
|
+
throw new Error("Github latest release url not found")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const path = await downloadUnzipTarball(
|
|
63
|
+
pluginLastReleaseTarballUrl,
|
|
64
|
+
pluginName,
|
|
65
|
+
headers
|
|
66
|
+
)
|
|
67
|
+
return await getPluginMetadata(path)
|
|
68
|
+
} catch (err: any) {
|
|
69
|
+
let errMsg = err?.message || err
|
|
70
|
+
if (errMsg === "unexpected response Not Found") {
|
|
71
|
+
errMsg = "Github release tarball not found"
|
|
72
|
+
}
|
|
73
|
+
throw new Error(errMsg)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { ObjectStoreBuckets } from "../../../constants"
|
|
2
|
+
import { loadJSFile } from "../../../utilities/fileSystem"
|
|
3
|
+
import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders"
|
|
4
|
+
import { getGlobalDB } from "@budibase/backend-core/tenancy"
|
|
5
|
+
import { validate } from "@budibase/backend-core/plugins"
|
|
6
|
+
import { generatePluginID, getPluginParams } from "../../../db/utils"
|
|
7
|
+
import {
|
|
8
|
+
uploadDirectory,
|
|
9
|
+
deleteFolder,
|
|
10
|
+
} from "@budibase/backend-core/objectStore"
|
|
11
|
+
import { PluginType, FileType, PluginSource } from "@budibase/types"
|
|
12
|
+
import env from "../../../environment"
|
|
13
|
+
|
|
14
|
+
export async function getPlugins(type?: PluginType) {
|
|
15
|
+
const db = getGlobalDB()
|
|
16
|
+
const response = await db.allDocs(
|
|
17
|
+
getPluginParams(null, {
|
|
18
|
+
include_docs: true,
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
const plugins = response.rows.map((row: any) => row.doc)
|
|
22
|
+
if (type) {
|
|
23
|
+
return plugins.filter((plugin: any) => plugin.schema?.type === type)
|
|
24
|
+
} else {
|
|
25
|
+
return plugins
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function upload(ctx: any) {
|
|
30
|
+
const plugins: FileType[] =
|
|
31
|
+
ctx.request.files.file.length > 1
|
|
32
|
+
? Array.from(ctx.request.files.file)
|
|
33
|
+
: [ctx.request.files.file]
|
|
34
|
+
try {
|
|
35
|
+
let docs = []
|
|
36
|
+
// can do single or multiple plugins
|
|
37
|
+
for (let plugin of plugins) {
|
|
38
|
+
const doc = await processPlugin(plugin, PluginSource.FILE)
|
|
39
|
+
docs.push(doc)
|
|
40
|
+
}
|
|
41
|
+
ctx.body = {
|
|
42
|
+
message: "Plugin(s) uploaded successfully",
|
|
43
|
+
plugins: docs,
|
|
44
|
+
}
|
|
45
|
+
} catch (err: any) {
|
|
46
|
+
const errMsg = err?.message ? err?.message : err
|
|
47
|
+
|
|
48
|
+
ctx.throw(400, `Failed to import plugin: ${errMsg}`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function create(ctx: any) {
|
|
53
|
+
const { source, url, headers, githubToken } = ctx.request.body
|
|
54
|
+
|
|
55
|
+
if (!env.SELF_HOSTED) {
|
|
56
|
+
ctx.throw(400, "Plugins not supported outside of self-host.")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
let metadata
|
|
61
|
+
let directory
|
|
62
|
+
// Generating random name as a backup and needed for url
|
|
63
|
+
let name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000)
|
|
64
|
+
|
|
65
|
+
switch (source) {
|
|
66
|
+
case PluginSource.NPM:
|
|
67
|
+
const { metadata: metadataNpm, directory: directoryNpm } =
|
|
68
|
+
await npmUpload(url, name)
|
|
69
|
+
metadata = metadataNpm
|
|
70
|
+
directory = directoryNpm
|
|
71
|
+
break
|
|
72
|
+
case PluginSource.GITHUB:
|
|
73
|
+
const { metadata: metadataGithub, directory: directoryGithub } =
|
|
74
|
+
await githubUpload(url, name, githubToken)
|
|
75
|
+
metadata = metadataGithub
|
|
76
|
+
directory = directoryGithub
|
|
77
|
+
break
|
|
78
|
+
case PluginSource.URL:
|
|
79
|
+
const headersObj = headers || {}
|
|
80
|
+
const { metadata: metadataUrl, directory: directoryUrl } =
|
|
81
|
+
await urlUpload(url, name, headersObj)
|
|
82
|
+
metadata = metadataUrl
|
|
83
|
+
directory = directoryUrl
|
|
84
|
+
break
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
validate(metadata?.schema)
|
|
88
|
+
|
|
89
|
+
const doc = await storePlugin(metadata, directory, source)
|
|
90
|
+
|
|
91
|
+
ctx.body = {
|
|
92
|
+
message: "Plugin uploaded successfully",
|
|
93
|
+
plugins: [doc],
|
|
94
|
+
}
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
const errMsg = err?.message ? err?.message : err
|
|
97
|
+
|
|
98
|
+
ctx.throw(400, `Failed to import plugin: ${errMsg}`)
|
|
99
|
+
}
|
|
100
|
+
ctx.status = 200
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function fetch(ctx: any) {
|
|
104
|
+
ctx.body = await getPlugins()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function destroy(ctx: any) {
|
|
108
|
+
const db = getGlobalDB()
|
|
109
|
+
const { pluginId } = ctx.params
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const plugin = await db.get(pluginId)
|
|
113
|
+
const bucketPath = `${plugin.name}/`
|
|
114
|
+
await deleteFolder(ObjectStoreBuckets.PLUGINS, bucketPath)
|
|
115
|
+
|
|
116
|
+
await db.remove(pluginId, plugin._rev)
|
|
117
|
+
} catch (err: any) {
|
|
118
|
+
const errMsg = err?.message ? err?.message : err
|
|
119
|
+
|
|
120
|
+
ctx.throw(400, `Failed to delete plugin: ${errMsg}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ctx.message = `Plugin ${ctx.params.pluginId} deleted.`
|
|
124
|
+
ctx.status = 200
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function storePlugin(
|
|
128
|
+
metadata: any,
|
|
129
|
+
directory: any,
|
|
130
|
+
source?: string
|
|
131
|
+
) {
|
|
132
|
+
const db = getGlobalDB()
|
|
133
|
+
const version = metadata.package.version,
|
|
134
|
+
name = metadata.package.name,
|
|
135
|
+
description = metadata.package.description
|
|
136
|
+
|
|
137
|
+
// first open the tarball into tmp directory
|
|
138
|
+
const bucketPath = `${name}/`
|
|
139
|
+
const files = await uploadDirectory(
|
|
140
|
+
ObjectStoreBuckets.PLUGINS,
|
|
141
|
+
directory,
|
|
142
|
+
bucketPath
|
|
143
|
+
)
|
|
144
|
+
const jsFile = files.find((file: any) => file.name.endsWith(".js"))
|
|
145
|
+
if (!jsFile) {
|
|
146
|
+
throw new Error(`Plugin missing .js file.`)
|
|
147
|
+
}
|
|
148
|
+
// validate the JS for a datasource
|
|
149
|
+
if (metadata.schema.type === PluginType.DATASOURCE) {
|
|
150
|
+
const js = loadJSFile(directory, jsFile.name)
|
|
151
|
+
// TODO: this isn't safe - but we need full node environment
|
|
152
|
+
// in future we should do this in a thread for safety
|
|
153
|
+
try {
|
|
154
|
+
eval(js)
|
|
155
|
+
} catch (err: any) {
|
|
156
|
+
const message = err?.message ? err.message : JSON.stringify(err)
|
|
157
|
+
throw new Error(`JS invalid: ${message}`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const jsFileName = jsFile.name
|
|
161
|
+
const pluginId = generatePluginID(name)
|
|
162
|
+
|
|
163
|
+
// overwrite existing docs entirely if they exist
|
|
164
|
+
let rev
|
|
165
|
+
try {
|
|
166
|
+
const existing = await db.get(pluginId)
|
|
167
|
+
rev = existing._rev
|
|
168
|
+
} catch (err) {
|
|
169
|
+
rev = undefined
|
|
170
|
+
}
|
|
171
|
+
let doc = {
|
|
172
|
+
_id: pluginId,
|
|
173
|
+
_rev: rev,
|
|
174
|
+
...metadata,
|
|
175
|
+
name,
|
|
176
|
+
version,
|
|
177
|
+
description,
|
|
178
|
+
jsUrl: `${bucketPath}${jsFileName}`,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (source) {
|
|
182
|
+
doc = {
|
|
183
|
+
...doc,
|
|
184
|
+
source,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const response = await db.put(doc)
|
|
189
|
+
return {
|
|
190
|
+
...doc,
|
|
191
|
+
_rev: response.rev,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function processPlugin(plugin: FileType, source?: string) {
|
|
196
|
+
if (!env.SELF_HOSTED) {
|
|
197
|
+
throw new Error("Plugins not supported outside of self-host.")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const { metadata, directory } = await fileUpload(plugin)
|
|
201
|
+
return await storePlugin(metadata, directory, source)
|
|
202
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPluginMetadata,
|
|
3
|
+
findFileRec,
|
|
4
|
+
extractTarball,
|
|
5
|
+
deleteFolderFileSystem,
|
|
6
|
+
} from "../../../utilities/fileSystem"
|
|
7
|
+
import fetch from "node-fetch"
|
|
8
|
+
import { join } from "path"
|
|
9
|
+
import { downloadUnzipTarball } from "./utils"
|
|
10
|
+
|
|
11
|
+
export async function npmUpload(url: string, name: string, headers = {}) {
|
|
12
|
+
let npmTarballUrl = url
|
|
13
|
+
let pluginName = name
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
!npmTarballUrl.includes("https://www.npmjs.com") &&
|
|
17
|
+
!npmTarballUrl.includes("https://registry.npmjs.org")
|
|
18
|
+
) {
|
|
19
|
+
throw new Error("The plugin origin must be from NPM")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!npmTarballUrl.includes(".tgz")) {
|
|
23
|
+
const npmPackageURl = url.replace(
|
|
24
|
+
"https://www.npmjs.com/package/",
|
|
25
|
+
"https://registry.npmjs.org/"
|
|
26
|
+
)
|
|
27
|
+
const response = await fetch(npmPackageURl)
|
|
28
|
+
if (response.status !== 200) {
|
|
29
|
+
throw new Error("NPM Package not found")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let npmDetails = await response.json()
|
|
33
|
+
pluginName = npmDetails.name
|
|
34
|
+
const npmVersion = npmDetails["dist-tags"].latest
|
|
35
|
+
npmTarballUrl = npmDetails?.versions?.[npmVersion]?.dist?.tarball
|
|
36
|
+
|
|
37
|
+
if (!npmTarballUrl) {
|
|
38
|
+
throw new Error("NPM tarball url not found")
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const path = await downloadUnzipTarball(npmTarballUrl, pluginName, headers)
|
|
43
|
+
const tarballPluginFile = findFileRec(path, ".tar.gz")
|
|
44
|
+
if (!tarballPluginFile) {
|
|
45
|
+
throw new Error("Tarball plugin file not found")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await extractTarball(tarballPluginFile, path)
|
|
50
|
+
deleteFolderFileSystem(join(path, "package"))
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
throw new Error(err)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return await getPluginMetadata(path)
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { downloadUnzipTarball } from "./utils"
|
|
2
|
+
import { getPluginMetadata } from "../../../utilities/fileSystem"
|
|
3
|
+
|
|
4
|
+
export async function urlUpload(url: string, name = "", headers = {}) {
|
|
5
|
+
if (!url.includes(".tar.gz")) {
|
|
6
|
+
throw new Error("Plugin must be compressed into a gzipped tarball.")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const path = await downloadUnzipTarball(url, name, headers)
|
|
10
|
+
|
|
11
|
+
return await getPluginMetadata(path)
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTempFolder,
|
|
3
|
+
downloadTarballDirect,
|
|
4
|
+
} from "../../../utilities/fileSystem"
|
|
5
|
+
|
|
6
|
+
export async function downloadUnzipTarball(
|
|
7
|
+
url: string,
|
|
8
|
+
name: string,
|
|
9
|
+
headers = {}
|
|
10
|
+
) {
|
|
11
|
+
try {
|
|
12
|
+
const path = createTempFolder(name)
|
|
13
|
+
await downloadTarballDirect(url, path, headers)
|
|
14
|
+
|
|
15
|
+
return path
|
|
16
|
+
} catch (e: any) {
|
|
17
|
+
throw new Error(e.message)
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/api/routes/plugin.ts
CHANGED
|
@@ -7,6 +7,7 @@ const router = new Router()
|
|
|
7
7
|
|
|
8
8
|
router
|
|
9
9
|
.post("/api/plugin/upload", authorized(BUILDER), controller.upload)
|
|
10
|
+
.post("/api/plugin", authorized(BUILDER), controller.create)
|
|
10
11
|
.get("/api/plugin", authorized(BUILDER), controller.fetch)
|
|
11
12
|
.delete("/api/plugin/:pluginId", authorized(BUILDER), controller.destroy)
|
|
12
13
|
|
package/src/module.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
streamUpload,
|
|
16
16
|
deleteFolder,
|
|
17
17
|
downloadTarball,
|
|
18
|
+
downloadTarballDirect,
|
|
18
19
|
deleteFiles,
|
|
19
20
|
} = require("./utilities")
|
|
20
21
|
const { updateClientLibrary } = require("./clientLibrary")
|
|
@@ -338,31 +339,57 @@ exports.cleanup = appIds => {
|
|
|
338
339
|
}
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
fs.
|
|
342
|
+
const createTempFolder = item => {
|
|
343
|
+
const path = join(budibaseTempDir(), item)
|
|
344
|
+
try {
|
|
345
|
+
// remove old tmp directories automatically - don't combine
|
|
346
|
+
if (fs.existsSync(path)) {
|
|
347
|
+
fs.rmSync(path, { recursive: true, force: true })
|
|
348
|
+
}
|
|
349
|
+
fs.mkdirSync(path)
|
|
350
|
+
} catch (err) {
|
|
351
|
+
throw new Error(`Path cannot be created: ${err.message}`)
|
|
349
352
|
}
|
|
350
|
-
|
|
353
|
+
|
|
354
|
+
return path
|
|
355
|
+
}
|
|
356
|
+
exports.createTempFolder = createTempFolder
|
|
357
|
+
|
|
358
|
+
const extractTarball = async (fromFilePath, toPath) => {
|
|
351
359
|
await tar.extract({
|
|
352
|
-
file:
|
|
353
|
-
C:
|
|
360
|
+
file: fromFilePath,
|
|
361
|
+
C: toPath,
|
|
354
362
|
})
|
|
363
|
+
}
|
|
364
|
+
exports.extractTarball = extractTarball
|
|
365
|
+
|
|
366
|
+
const getPluginMetadata = async path => {
|
|
355
367
|
let metadata = {}
|
|
356
368
|
try {
|
|
357
369
|
const pkg = fs.readFileSync(join(path, "package.json"), "utf8")
|
|
358
370
|
const schema = fs.readFileSync(join(path, "schema.json"), "utf8")
|
|
371
|
+
|
|
359
372
|
metadata.schema = JSON.parse(schema)
|
|
360
373
|
metadata.package = JSON.parse(pkg)
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
!metadata.package.name ||
|
|
377
|
+
!metadata.package.version ||
|
|
378
|
+
!metadata.package.description
|
|
379
|
+
) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
"package.json is missing one of 'name', 'version' or 'description'."
|
|
382
|
+
)
|
|
383
|
+
}
|
|
361
384
|
} catch (err) {
|
|
362
|
-
throw new Error(
|
|
385
|
+
throw new Error(
|
|
386
|
+
`Unable to process schema.json/package.json in plugin. ${err.message}`
|
|
387
|
+
)
|
|
363
388
|
}
|
|
389
|
+
|
|
364
390
|
return { metadata, directory: path }
|
|
365
391
|
}
|
|
392
|
+
exports.getPluginMetadata = getPluginMetadata
|
|
366
393
|
|
|
367
394
|
exports.getDatasourcePlugin = async (name, url, hash) => {
|
|
368
395
|
if (!fs.existsSync(DATASOURCE_PATH)) {
|
|
@@ -396,6 +423,38 @@ exports.getDatasourcePlugin = async (name, url, hash) => {
|
|
|
396
423
|
}
|
|
397
424
|
}
|
|
398
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Find for a file recursively from start path applying filter, return first match
|
|
428
|
+
*/
|
|
429
|
+
exports.findFileRec = (startPath, filter) => {
|
|
430
|
+
if (!fs.existsSync(startPath)) {
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const files = fs.readdirSync(startPath)
|
|
435
|
+
for (let i = 0, len = files.length; i < len; i++) {
|
|
436
|
+
const filename = join(startPath, files[i])
|
|
437
|
+
const stat = fs.lstatSync(filename)
|
|
438
|
+
|
|
439
|
+
if (stat.isDirectory()) {
|
|
440
|
+
return exports.findFileRec(filename, filter)
|
|
441
|
+
} else if (filename.endsWith(filter)) {
|
|
442
|
+
return filename
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Remove a folder which is not empty from the file system
|
|
449
|
+
*/
|
|
450
|
+
exports.deleteFolderFileSystem = path => {
|
|
451
|
+
if (!fs.existsSync(path)) {
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
fs.rmSync(path, { recursive: true, force: true })
|
|
456
|
+
}
|
|
457
|
+
|
|
399
458
|
/**
|
|
400
459
|
* Full function definition for below can be found in the utilities.
|
|
401
460
|
*/
|
|
@@ -403,5 +462,6 @@ exports.upload = upload
|
|
|
403
462
|
exports.retrieve = retrieve
|
|
404
463
|
exports.retrieveToTmp = retrieveToTmp
|
|
405
464
|
exports.deleteFiles = deleteFiles
|
|
465
|
+
exports.downloadTarballDirect = downloadTarballDirect
|
|
406
466
|
exports.TOP_LEVEL_PATH = TOP_LEVEL_PATH
|
|
407
467
|
exports.NODE_MODULES_PATH = NODE_MODULES_PATH
|