@budibase/cli 1.3.14 → 1.3.15-alpha.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/cli",
3
- "version": "1.3.14",
3
+ "version": "1.3.15-alpha.0",
4
4
  "description": "Budibase CLI, for developers, self hosting and migrations.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -26,14 +26,18 @@
26
26
  "outputPath": "build"
27
27
  },
28
28
  "dependencies": {
29
- "@budibase/backend-core": "^1.3.14",
29
+ "@budibase/backend-core": "1.3.15-alpha.0",
30
+ "@budibase/string-templates": "1.3.15-alpha.0",
31
+ "@budibase/types": "1.3.15-alpha.0",
30
32
  "axios": "0.21.2",
31
33
  "chalk": "4.1.0",
32
34
  "cli-progress": "3.11.2",
33
35
  "commander": "7.1.0",
34
36
  "docker-compose": "0.23.6",
35
37
  "dotenv": "16.0.1",
38
+ "download": "8.0.0",
36
39
  "inquirer": "8.0.0",
40
+ "joi": "17.6.0",
37
41
  "lookpath": "1.1.0",
38
42
  "node-fetch": "2",
39
43
  "pkg": "5.7.0",
@@ -48,5 +52,5 @@
48
52
  "eslint": "^7.20.0",
49
53
  "renamer": "^4.0.0"
50
54
  },
51
- "gitHead": "a38626cf662d66aa8c04fb1d42dc7bc353ce7c31"
55
+ "gitHead": "38f3c6d1d713f5e1bd18fdd81bdffb9bf7d4f9d3"
52
56
  }
package/src/constants.js CHANGED
@@ -3,6 +3,7 @@ exports.CommandWords = {
3
3
  HOSTING: "hosting",
4
4
  ANALYTICS: "analytics",
5
5
  HELP: "help",
6
+ PLUGIN: "plugins",
6
7
  }
7
8
 
8
9
  exports.InitTypes = {
@@ -0,0 +1 @@
1
+ process.env.NO_JS = "1"
package/src/exec.js ADDED
@@ -0,0 +1,27 @@
1
+ const util = require("util")
2
+ const exec = util.promisify(require("child_process").exec)
3
+
4
+ exports.exec = async (command, dir = "./") => {
5
+ const { stdout } = await exec(command, { cwd: dir })
6
+ return stdout
7
+ }
8
+
9
+ exports.utilityInstalled = async utilName => {
10
+ try {
11
+ await exports.exec(`${utilName} --version`)
12
+ return true
13
+ } catch (err) {
14
+ return false
15
+ }
16
+ }
17
+
18
+ exports.runPkgCommand = async (command, dir = "./") => {
19
+ const yarn = await exports.utilityInstalled("yarn")
20
+ const npm = await exports.utilityInstalled("npm")
21
+ if (!yarn && !npm) {
22
+ throw new Error("Must have yarn or npm installed to run build.")
23
+ }
24
+ const npmCmd = command === "install" ? `npm ${command}` : `npm run ${command}`
25
+ const cmd = yarn ? `yarn ${command}` : npmCmd
26
+ await exports.exec(cmd, dir)
27
+ }
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  require("./prebuilds")
3
+ require("./environment")
3
4
  const { getCommands } = require("./options")
4
5
  const { Command } = require("commander")
5
6
  const { getHelpDescription } = require("./utils")
package/src/options.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const analytics = require("./analytics")
2
2
  const hosting = require("./hosting")
3
3
  const backups = require("./backups")
4
+ const plugins = require("./plugins")
4
5
 
5
6
  exports.getCommands = () => {
6
- return [hosting.command, analytics.command, backups.command]
7
+ return [hosting.command, analytics.command, backups.command, plugins.command]
7
8
  }
@@ -0,0 +1,6 @@
1
+ exports.PluginTypes = {
2
+ COMPONENT: "component",
3
+ DATASOURCE: "datasource",
4
+ }
5
+
6
+ exports.PLUGIN_TYPES_ARR = Object.values(exports.PluginTypes)
@@ -0,0 +1,132 @@
1
+ const Command = require("../structures/Command")
2
+ const { CommandWords } = require("../constants")
3
+ const { getSkeleton, fleshOutSkeleton } = require("./skeleton")
4
+ const questions = require("../questions")
5
+ const fs = require("fs")
6
+ const { PLUGIN_TYPES_ARR } = require("./constants")
7
+ const { validate } = require("./validate")
8
+ const { runPkgCommand } = require("../exec")
9
+ const { join } = require("path")
10
+ const { success, error, info } = require("../utils")
11
+
12
+ function checkInPlugin() {
13
+ if (!fs.existsSync("package.json")) {
14
+ throw new Error(
15
+ "Please run in a plugin directory - must contain package.json"
16
+ )
17
+ }
18
+ if (!fs.existsSync("schema.json")) {
19
+ throw new Error(
20
+ "Please run in a plugin directory - must contain schema.json"
21
+ )
22
+ }
23
+ }
24
+
25
+ async function init(opts) {
26
+ const type = opts["init"] || opts
27
+ if (!type || !PLUGIN_TYPES_ARR.includes(type)) {
28
+ console.log(
29
+ error(
30
+ "Please provide a type to init, either 'component' or 'datasource'."
31
+ )
32
+ )
33
+ return
34
+ }
35
+ console.log(info("Lets get some details about your new plugin:"))
36
+ const name = await questions.string("Name", `budibase-${type}`)
37
+ if (fs.existsSync(name)) {
38
+ console.log(
39
+ error("Directory by plugin name already exists, pick a new name.")
40
+ )
41
+ return
42
+ }
43
+ const desc = await questions.string(
44
+ "Description",
45
+ `An amazing Budibase ${type}!`
46
+ )
47
+ const version = await questions.string("Version", "1.0.0")
48
+ // get the skeleton
49
+ console.log(info("Retrieving project..."))
50
+ await getSkeleton(type, name)
51
+ await fleshOutSkeleton(type, name, desc, version)
52
+ console.log(info("Installing dependencies..."))
53
+ await runPkgCommand("install", join(process.cwd(), name))
54
+ console.log(info(`Plugin created in directory "${name}"`))
55
+ }
56
+
57
+ async function verify() {
58
+ // will throw errors if not acceptable
59
+ checkInPlugin()
60
+ console.log(info("Verifying plugin..."))
61
+ const schema = fs.readFileSync("schema.json", "utf8")
62
+ const pkg = fs.readFileSync("package.json", "utf8")
63
+ let name, version
64
+ try {
65
+ const schemaJson = JSON.parse(schema)
66
+ const pkgJson = JSON.parse(pkg)
67
+ if (!pkgJson.name || !pkgJson.version || !pkgJson.description) {
68
+ throw new Error(
69
+ "package.json is missing one of 'name', 'version' or 'description'."
70
+ )
71
+ }
72
+ name = pkgJson.name
73
+ version = pkgJson.version
74
+ validate(schemaJson)
75
+ return { name, version }
76
+ } catch (err) {
77
+ if (err && err.message && err.message.includes("not valid JSON")) {
78
+ console.log(error(`schema.json is not valid JSON: ${err.message}`))
79
+ } else {
80
+ console.log(error(`Invalid schema/package.json: ${err.message}`))
81
+ }
82
+ }
83
+ }
84
+
85
+ async function build() {
86
+ const verified = await verify()
87
+ if (!verified.name) {
88
+ return
89
+ }
90
+ console.log(success("Verified!"))
91
+ console.log(info("Building plugin..."))
92
+ await runPkgCommand("build")
93
+ const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
94
+ console.log(success(`Build complete - output in: ${output}`))
95
+ }
96
+
97
+ async function watch() {
98
+ const verified = await verify()
99
+ if (!verified.name) {
100
+ return
101
+ }
102
+ const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
103
+ console.log(info(`Watching - build in: ${output}`))
104
+ try {
105
+ await runPkgCommand("watch")
106
+ } catch (err) {
107
+ // always errors when user escapes
108
+ console.log(success("Watch exited."))
109
+ }
110
+ }
111
+
112
+ const command = new Command(`${CommandWords.PLUGIN}`)
113
+ .addHelp(
114
+ "Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
115
+ )
116
+ .addSubOption(
117
+ "--init [type]",
118
+ "Init a new plugin project, with a type of either component or datasource.",
119
+ init
120
+ )
121
+ .addSubOption(
122
+ "--build",
123
+ "Build your plugin, this will verify and produce a final tarball for your project.",
124
+ build
125
+ )
126
+ .addSubOption(
127
+ "--watch",
128
+ "Automatically build any changes to your plugin.",
129
+ watch
130
+ )
131
+
132
+ exports.command = command
@@ -0,0 +1,60 @@
1
+ const fetch = require("node-fetch")
2
+ const download = require("download")
3
+ const fs = require("fs")
4
+ const os = require("os")
5
+ const { join } = require("path")
6
+ const tar = require("tar")
7
+ const { processStringSync } = require("@budibase/string-templates")
8
+
9
+ const HBS_FILES = ["package.json.hbs", "schema.json.hbs", "README.md.hbs"]
10
+
11
+ async function getSkeletonUrl(type) {
12
+ const resp = await fetch(
13
+ "https://api.github.com/repos/budibase/budibase-skeleton/releases/latest"
14
+ )
15
+ if (resp.status >= 300) {
16
+ throw new Error("Failed to retrieve skeleton metadata")
17
+ }
18
+ const json = await resp.json()
19
+ for (let asset of json["assets"]) {
20
+ if (asset.name && asset.name.includes(type)) {
21
+ return asset["browser_download_url"]
22
+ }
23
+ }
24
+ throw new Error("No skeleton found in latest release.")
25
+ }
26
+
27
+ exports.getSkeleton = async (type, name) => {
28
+ const url = await getSkeletonUrl(type)
29
+ const tarballFile = join(os.tmpdir(), "skeleton.tar.gz")
30
+
31
+ // download the full skeleton tarball
32
+ fs.writeFileSync(tarballFile, await download(url))
33
+ fs.mkdirSync(name)
34
+ // extract it and get what we need
35
+ await tar.extract({
36
+ file: tarballFile,
37
+ C: name,
38
+ })
39
+ // clear up
40
+ fs.rmSync(tarballFile)
41
+ }
42
+
43
+ exports.fleshOutSkeleton = async (type, name, description, version) => {
44
+ for (let file of HBS_FILES) {
45
+ const oldFile = join(name, file),
46
+ newFile = join(name, file.substring(0, file.length - 4))
47
+ const hbsContents = fs.readFileSync(oldFile, "utf8")
48
+ if (!hbsContents) {
49
+ continue
50
+ }
51
+ const output = processStringSync(hbsContents, {
52
+ name,
53
+ description,
54
+ version,
55
+ })
56
+ // write the updated file and remove the HBS file
57
+ fs.writeFileSync(newFile, output)
58
+ fs.rmSync(oldFile)
59
+ }
60
+ }
@@ -0,0 +1,91 @@
1
+ const { PluginTypes } = require("./constants")
2
+ const { DatasourceFieldType, QueryType } = require("@budibase/types")
3
+ const joi = require("joi")
4
+
5
+ const DATASOURCE_TYPES = [
6
+ "Relational",
7
+ "Non-relational",
8
+ "Spreadsheet",
9
+ "Object store",
10
+ "Graph",
11
+ "API",
12
+ ]
13
+
14
+ function runJoi(validator, schema) {
15
+ const { error } = validator.validate(schema)
16
+ if (error) {
17
+ throw error
18
+ }
19
+ }
20
+
21
+ function validateComponent(schema) {
22
+ const validator = joi.object({
23
+ type: joi.string().allow("component").required(),
24
+ metadata: joi.object().unknown(true).required(),
25
+ hash: joi.string().optional(),
26
+ version: joi.string().optional(),
27
+ schema: joi
28
+ .object({
29
+ name: joi.string().required(),
30
+ settings: joi.array().items(joi.object().unknown(true)).required(),
31
+ })
32
+ .unknown(true),
33
+ })
34
+ runJoi(validator, schema)
35
+ }
36
+
37
+ function validateDatasource(schema) {
38
+ const fieldValidator = joi.object({
39
+ type: joi
40
+ .string()
41
+ .allow(...Object.values(DatasourceFieldType))
42
+ .required(),
43
+ required: joi.boolean().required(),
44
+ default: joi.any(),
45
+ display: joi.string(),
46
+ })
47
+
48
+ const queryValidator = joi
49
+ .object({
50
+ type: joi.string().allow(...Object.values(QueryType)),
51
+ fields: joi.object().pattern(joi.string(), fieldValidator),
52
+ })
53
+ .required()
54
+
55
+ const validator = joi.object({
56
+ type: joi.string().allow("datasource").required(),
57
+ metadata: joi.object().unknown(true).required(),
58
+ hash: joi.string().optional(),
59
+ version: joi.string().optional(),
60
+ schema: joi.object({
61
+ docs: joi.string(),
62
+ friendlyName: joi.string().required(),
63
+ type: joi.string().allow(...DATASOURCE_TYPES),
64
+ description: joi.string().required(),
65
+ datasource: joi.object().pattern(joi.string(), fieldValidator).required(),
66
+ query: joi
67
+ .object({
68
+ create: queryValidator,
69
+ read: queryValidator,
70
+ update: queryValidator,
71
+ delete: queryValidator,
72
+ })
73
+ .unknown(true)
74
+ .required(),
75
+ }),
76
+ })
77
+ runJoi(validator, schema)
78
+ }
79
+
80
+ exports.validate = schema => {
81
+ switch (schema.type) {
82
+ case PluginTypes.COMPONENT:
83
+ validateComponent(schema)
84
+ break
85
+ case PluginTypes.DATASOURCE:
86
+ validateDatasource(schema)
87
+ break
88
+ default:
89
+ throw new Error(`Unknown plugin type - check schema.json: ${schema.type}`)
90
+ }
91
+ }
package/src/prebuilds.js CHANGED
@@ -1,11 +1,15 @@
1
1
  const os = require("os")
2
2
  const { join } = require("path")
3
3
  const fs = require("fs")
4
+ const { error } = require("./utils")
4
5
  const PREBUILDS = "prebuilds"
5
6
  const ARCH = `${os.platform()}-${os.arch()}`
6
7
  const PREBUILD_DIR = join(process.execPath, "..", PREBUILDS, ARCH)
7
8
 
8
- checkForBinaries()
9
+ // running as built CLI pkg bundle
10
+ if (!process.argv[0].includes("node")) {
11
+ checkForBinaries()
12
+ }
9
13
 
10
14
  function checkForBinaries() {
11
15
  const readDir = join(__filename, "..", "..", PREBUILDS, ARCH)
@@ -22,7 +26,15 @@ function checkForBinaries() {
22
26
  }
23
27
  }
24
28
 
25
- function cleanup() {
29
+ function cleanup(evt) {
30
+ if (evt && evt.errno) {
31
+ console.error(
32
+ error(
33
+ "Failed to run CLI command - please report with the following message:"
34
+ )
35
+ )
36
+ console.error(error(evt))
37
+ }
26
38
  if (fs.existsSync(PREBUILD_DIR)) {
27
39
  fs.rmSync(PREBUILD_DIR, { recursive: true })
28
40
  }