@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.
Files changed (176) hide show
  1. package/builder/assets/{index.f5c54dd2.css → index.ecdac070.css} +2 -2
  2. package/builder/assets/{index.b266d29f.js → index.ed9ae6a1.js} +288 -288
  3. package/builder/index.html +2 -2
  4. package/coverage/clover.xml +2 -2
  5. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  6. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  7. package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  10. package/coverage/lcov-report/api/controllers/component.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/datasource.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/index.html +1 -1
  14. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  15. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  16. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  17. package/coverage/lcov-report/api/controllers/migrations.js.html +1 -1
  18. package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/query/index.html +1 -1
  20. package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  22. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  23. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  24. package/coverage/lcov-report/api/controllers/row/index.html +1 -1
  25. package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
  26. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
  27. package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +1 -1
  28. package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
  30. package/coverage/lcov-report/api/controllers/script.js.html +1 -1
  31. package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +1 -1
  32. package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
  33. package/coverage/lcov-report/api/controllers/table/index.html +1 -1
  34. package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
  35. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  36. package/coverage/lcov-report/api/controllers/user.js.html +1 -1
  37. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  38. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  39. package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
  40. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  42. package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
  43. package/coverage/lcov-report/api/index.html +1 -1
  44. package/coverage/lcov-report/api/index.js.html +1 -1
  45. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  46. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  47. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  48. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  49. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  50. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/index.html +1 -1
  56. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/migrations.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/utils/index.html +1 -1
  70. package/coverage/lcov-report/api/routes/utils/validators.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  73. package/coverage/lcov-report/automations/actions.js.html +1 -1
  74. package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
  75. package/coverage/lcov-report/automations/bullboard.js.html +1 -1
  76. package/coverage/lcov-report/automations/index.html +1 -1
  77. package/coverage/lcov-report/automations/index.js.html +1 -1
  78. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  79. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  80. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  81. package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
  82. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  83. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/index.html +1 -1
  85. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  86. package/coverage/lcov-report/automations/steps/loop.js.html +1 -1
  87. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  92. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  94. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  95. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  96. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  97. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  98. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  99. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  100. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  101. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  102. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  103. package/coverage/lcov-report/automations/triggers.js.html +1 -1
  104. package/coverage/lcov-report/constants/index.html +1 -1
  105. package/coverage/lcov-report/constants/index.js.html +1 -1
  106. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  107. package/coverage/lcov-report/constants/screens.js.html +1 -1
  108. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  109. package/coverage/lcov-report/db/index.html +1 -1
  110. package/coverage/lcov-report/db/index.js.html +1 -1
  111. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  112. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  113. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  114. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  115. package/coverage/lcov-report/db/newid.js.html +1 -1
  116. package/coverage/lcov-report/db/utils.js.html +1 -1
  117. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  118. package/coverage/lcov-report/events/index.html +1 -1
  119. package/coverage/lcov-report/events/index.js.html +1 -1
  120. package/coverage/lcov-report/events/utils.js.html +1 -1
  121. package/coverage/lcov-report/index.html +1 -1
  122. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  123. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  124. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  125. package/coverage/lcov-report/middleware/builder.js.html +1 -1
  126. package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
  127. package/coverage/lcov-report/middleware/index.html +1 -1
  128. package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
  129. package/coverage/lcov-report/middleware/publicApi.js.html +1 -1
  130. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  131. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  132. package/coverage/lcov-report/middleware/utils.js.html +1 -1
  133. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  134. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  135. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  136. package/coverage/lcov-report/utilities/global.js.html +1 -1
  137. package/coverage/lcov-report/utilities/index.html +1 -1
  138. package/coverage/lcov-report/utilities/index.js.html +1 -1
  139. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +1 -1
  140. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  141. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  142. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  143. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  144. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  145. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  146. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  147. package/coverage/lcov-report/utilities/security.js.html +1 -1
  148. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  149. package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
  150. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
  151. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
  152. package/coverage/lcov-report/utilities/users.js.html +1 -1
  153. package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
  154. package/dist/api/controllers/plugin/file.js +24 -0
  155. package/dist/api/controllers/plugin/github.js +69 -0
  156. package/dist/api/controllers/{plugin.js → plugin/index.js} +83 -13
  157. package/dist/api/controllers/plugin/npm.js +58 -0
  158. package/dist/api/controllers/plugin/uploaders.js +11 -0
  159. package/dist/api/controllers/plugin/url.js +24 -0
  160. package/dist/api/controllers/plugin/utils.js +26 -0
  161. package/dist/api/routes/plugin.js +1 -0
  162. package/dist/package.json +6 -6
  163. package/dist/tsconfig.build.tsbuildinfo +1 -1
  164. package/dist/utilities/fileSystem/index.js +56 -12
  165. package/package.json +7 -7
  166. package/src/api/controllers/plugin/file.ts +15 -0
  167. package/src/api/controllers/plugin/github.ts +75 -0
  168. package/src/api/controllers/plugin/index.ts +202 -0
  169. package/src/api/controllers/plugin/npm.ts +56 -0
  170. package/src/api/controllers/plugin/uploaders.ts +4 -0
  171. package/src/api/controllers/plugin/url.ts +12 -0
  172. package/src/api/controllers/plugin/utils.ts +19 -0
  173. package/src/api/routes/plugin.ts +1 -0
  174. package/src/module.d.ts +1 -0
  175. package/src/utilities/fileSystem/index.js +72 -12
  176. 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
- exports.extractPluginTarball = (file) => __awaiter(void 0, void 0, void 0, function* () {
299
- if (!file.name.endsWith(".tar.gz")) {
300
- throw new Error("Plugin must be compressed into a gzipped tarball.");
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
- const path = join(budibaseTempDir(), file.name.split(".tar.gz")[0]);
303
- // remove old tmp directories automatically - don't combine
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
- fs.mkdirSync(path);
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: file.path,
310
- C: path,
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("Unable to process schema.json/package.json in plugin.");
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.0",
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.0",
81
- "@budibase/client": "1.3.15-alpha.0",
82
- "@budibase/pro": "1.3.13",
83
- "@budibase/string-templates": "1.3.15-alpha.0",
84
- "@budibase/types": "1.3.15-alpha.0",
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": "38f3c6d1d713f5e1bd18fdd81bdffb9bf7d4f9d3"
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,4 @@
1
+ export { fileUpload } from "./file"
2
+ export { githubUpload } from "./github"
3
+ export { npmUpload } from "./npm"
4
+ export { urlUpload } from "./url"
@@ -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
+ }
@@ -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
@@ -11,3 +11,4 @@ declare module "@budibase/backend-core/encryption"
11
11
  declare module "@budibase/backend-core/utils"
12
12
  declare module "@budibase/backend-core/redis"
13
13
  declare module "@budibase/backend-core/objectStore"
14
+ declare module "@budibase/backend-core/plugins"
@@ -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
- exports.extractPluginTarball = async file => {
342
- if (!file.name.endsWith(".tar.gz")) {
343
- throw new Error("Plugin must be compressed into a gzipped tarball.")
344
- }
345
- const path = join(budibaseTempDir(), file.name.split(".tar.gz")[0])
346
- // remove old tmp directories automatically - don't combine
347
- if (fs.existsSync(path)) {
348
- fs.rmSync(path, { recursive: true, force: true })
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
- fs.mkdirSync(path)
353
+
354
+ return path
355
+ }
356
+ exports.createTempFolder = createTempFolder
357
+
358
+ const extractTarball = async (fromFilePath, toPath) => {
351
359
  await tar.extract({
352
- file: file.path,
353
- C: path,
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("Unable to process schema.json/package.json in plugin.")
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