@budibase/cli 1.3.17 → 1.3.19-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.17",
3
+ "version": "1.3.19-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.17",
29
+ "@budibase/backend-core": "1.3.19-alpha.0",
30
+ "@budibase/string-templates": "1.3.19-alpha.0",
31
+ "@budibase/types": "1.3.19-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": "01d7c9d994aa66a1d08ab71acc9e6c8eab825a47"
55
+ "gitHead": "04749bf93a17516f8f71d44b49995b2d50b080bb"
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,2 @@
1
+ process.env.NO_JS = "1"
2
+ process.env.JS_BCRYPT = "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,157 @@
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_TYPE_ARR } = require("@budibase/types")
7
+ const { validate } = require("@budibase/backend-core/plugins")
8
+ const { runPkgCommand } = require("../exec")
9
+ const { join } = require("path")
10
+ const { success, error, info, moveDirectory } = 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 askAboutTopLevel(name) {
26
+ const files = fs.readdirSync(process.cwd())
27
+ // we are in an empty git repo, don't ask
28
+ if (files.find(file => file === ".git")) {
29
+ return false
30
+ } else {
31
+ console.log(
32
+ info(`By default the plugin will be created in the directory "${name}"`)
33
+ )
34
+ console.log(
35
+ info(
36
+ "if you are already in an empty directory, such as a new Git repo, you can disable this functionality."
37
+ )
38
+ )
39
+ return questions.confirmation("Create top level directory?")
40
+ }
41
+ }
42
+
43
+ async function init(opts) {
44
+ const type = opts["init"] || opts
45
+ if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
46
+ console.log(
47
+ error(
48
+ "Please provide a type to init, either 'component' or 'datasource'."
49
+ )
50
+ )
51
+ return
52
+ }
53
+ console.log(info("Lets get some details about your new plugin:"))
54
+ const name = await questions.string("Name", `budibase-${type}`)
55
+ if (fs.existsSync(name)) {
56
+ console.log(
57
+ error("Directory by plugin name already exists, pick a new name.")
58
+ )
59
+ return
60
+ }
61
+ const desc = await questions.string(
62
+ "Description",
63
+ `An amazing Budibase ${type}!`
64
+ )
65
+ const version = await questions.string("Version", "1.0.0")
66
+ const topLevel = await askAboutTopLevel(name)
67
+ // get the skeleton
68
+ console.log(info("Retrieving project..."))
69
+ await getSkeleton(type, name)
70
+ await fleshOutSkeleton(type, name, desc, version)
71
+ console.log(info("Installing dependencies..."))
72
+ await runPkgCommand("install", join(process.cwd(), name))
73
+ // if no parent directory desired move to cwd
74
+ if (!topLevel) {
75
+ moveDirectory(name, process.cwd())
76
+ console.log(info(`Plugin created in current directory.`))
77
+ } else {
78
+ console.log(info(`Plugin created in directory "${name}"`))
79
+ }
80
+ }
81
+
82
+ async function verify() {
83
+ // will throw errors if not acceptable
84
+ checkInPlugin()
85
+ console.log(info("Verifying plugin..."))
86
+ const schema = fs.readFileSync("schema.json", "utf8")
87
+ const pkg = fs.readFileSync("package.json", "utf8")
88
+ let name, version
89
+ try {
90
+ const schemaJson = JSON.parse(schema)
91
+ const pkgJson = JSON.parse(pkg)
92
+ if (!pkgJson.name || !pkgJson.version || !pkgJson.description) {
93
+ throw new Error(
94
+ "package.json is missing one of 'name', 'version' or 'description'."
95
+ )
96
+ }
97
+ name = pkgJson.name
98
+ version = pkgJson.version
99
+ validate(schemaJson)
100
+ return { name, version }
101
+ } catch (err) {
102
+ if (err && err.message && err.message.includes("not valid JSON")) {
103
+ console.log(error(`schema.json is not valid JSON: ${err.message}`))
104
+ } else {
105
+ console.log(error(`Invalid schema/package.json: ${err.message}`))
106
+ }
107
+ }
108
+ }
109
+
110
+ async function build() {
111
+ const verified = await verify()
112
+ if (!verified.name) {
113
+ return
114
+ }
115
+ console.log(success("Verified!"))
116
+ console.log(info("Building plugin..."))
117
+ await runPkgCommand("build")
118
+ const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
119
+ console.log(success(`Build complete - output in: ${output}`))
120
+ }
121
+
122
+ async function watch() {
123
+ const verified = await verify()
124
+ if (!verified.name) {
125
+ return
126
+ }
127
+ const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
128
+ console.log(info(`Watching - build in: ${output}`))
129
+ try {
130
+ await runPkgCommand("watch")
131
+ } catch (err) {
132
+ // always errors when user escapes
133
+ console.log(success("Watch exited."))
134
+ }
135
+ }
136
+
137
+ const command = new Command(`${CommandWords.PLUGIN}`)
138
+ .addHelp(
139
+ "Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
140
+ )
141
+ .addSubOption(
142
+ "--init [type]",
143
+ "Init a new plugin project, with a type of either component or datasource.",
144
+ init
145
+ )
146
+ .addSubOption(
147
+ "--build",
148
+ "Build your plugin, this will verify and produce a final tarball for your project.",
149
+ build
150
+ )
151
+ .addSubOption(
152
+ "--watch",
153
+ "Automatically build any changes to your plugin.",
154
+ watch
155
+ )
156
+
157
+ 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
+ }
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,18 @@ function checkForBinaries() {
22
26
  }
23
27
  }
24
28
 
25
- function cleanup() {
29
+ function cleanup(evt) {
30
+ if (!isNaN(evt)) {
31
+ return
32
+ }
33
+ if (evt) {
34
+ console.error(
35
+ error(
36
+ "Failed to run CLI command - please report with the following message:"
37
+ )
38
+ )
39
+ console.error(error(evt))
40
+ }
26
41
  if (fs.existsSync(PREBUILD_DIR)) {
27
42
  fs.rmSync(PREBUILD_DIR, { recursive: true })
28
43
  }
package/src/utils.js CHANGED
@@ -3,6 +3,7 @@ const fs = require("fs")
3
3
  const axios = require("axios")
4
4
  const path = require("path")
5
5
  const progress = require("cli-progress")
6
+ const { join } = require("path")
6
7
 
7
8
  exports.downloadFile = async (url, filePath) => {
8
9
  filePath = path.resolve(filePath)
@@ -67,3 +68,19 @@ exports.progressBar = total => {
67
68
  exports.checkSlashesInUrl = url => {
68
69
  return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2")
69
70
  }
71
+
72
+ exports.moveDirectory = (oldPath, newPath) => {
73
+ const files = fs.readdirSync(oldPath)
74
+ // check any file exists already
75
+ for (let file of files) {
76
+ if (fs.existsSync(join(newPath, file))) {
77
+ throw new Error(
78
+ "Unable to remove top level directory - some skeleton files already exist."
79
+ )
80
+ }
81
+ }
82
+ for (let file of files) {
83
+ fs.renameSync(join(oldPath, file), join(newPath, file))
84
+ }
85
+ fs.rmdirSync(oldPath)
86
+ }