@budibase/cli 0.0.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 (67) hide show
  1. package/.eslintrc +12 -0
  2. package/LICENSE.md +674 -0
  3. package/dist/analytics/Client.js +32 -0
  4. package/dist/analytics/index.js +55 -0
  5. package/dist/backups/index.js +119 -0
  6. package/dist/backups/objectStore.js +82 -0
  7. package/dist/backups/utils.js +110 -0
  8. package/dist/constants.js +9 -0
  9. package/dist/core/db.js +55 -0
  10. package/dist/environment.js +4 -0
  11. package/dist/events.js +13 -0
  12. package/dist/exec.js +49 -0
  13. package/dist/hosting/genUser.js +32 -0
  14. package/dist/hosting/index.js +21 -0
  15. package/dist/hosting/init.js +117 -0
  16. package/dist/hosting/makeFiles.js +144 -0
  17. package/dist/hosting/start.js +63 -0
  18. package/dist/hosting/status.js +30 -0
  19. package/dist/hosting/stop.js +30 -0
  20. package/dist/hosting/types.js +2 -0
  21. package/dist/hosting/update.js +62 -0
  22. package/dist/hosting/utils.js +159 -0
  23. package/dist/hosting/watch.js +47 -0
  24. package/dist/index.js +38 -0
  25. package/dist/options.js +14 -0
  26. package/dist/plugins/index.js +199 -0
  27. package/dist/plugins/skeleton.js +75 -0
  28. package/dist/prebuilds.js +46 -0
  29. package/dist/questions.js +58 -0
  30. package/dist/structures/Command.js +75 -0
  31. package/dist/structures/ConfigManager.js +39 -0
  32. package/dist/tsconfig.build.tsbuildinfo +1 -0
  33. package/dist/utils.js +126 -0
  34. package/package.json +62 -0
  35. package/src/analytics/Client.ts +33 -0
  36. package/src/analytics/index.ts +60 -0
  37. package/src/backups/index.ts +130 -0
  38. package/src/backups/objectStore.ts +69 -0
  39. package/src/backups/utils.ts +99 -0
  40. package/src/constants.ts +4 -0
  41. package/src/core/db.ts +39 -0
  42. package/src/environment.ts +3 -0
  43. package/src/events.ts +11 -0
  44. package/src/exec.ts +27 -0
  45. package/src/hosting/genUser.ts +23 -0
  46. package/src/hosting/index.ts +48 -0
  47. package/src/hosting/init.ts +76 -0
  48. package/src/hosting/makeFiles.ts +140 -0
  49. package/src/hosting/start.ts +30 -0
  50. package/src/hosting/status.ts +13 -0
  51. package/src/hosting/stop.ts +13 -0
  52. package/src/hosting/types.ts +4 -0
  53. package/src/hosting/update.ts +55 -0
  54. package/src/hosting/utils.ts +127 -0
  55. package/src/hosting/watch.ts +37 -0
  56. package/src/index.ts +27 -0
  57. package/src/options.ts +8 -0
  58. package/src/plugins/index.ts +198 -0
  59. package/src/plugins/skeleton.ts +65 -0
  60. package/src/prebuilds.ts +50 -0
  61. package/src/questions.ts +40 -0
  62. package/src/structures/Command.ts +94 -0
  63. package/src/structures/ConfigManager.ts +49 -0
  64. package/src/utils.ts +112 -0
  65. package/start.sh +3 -0
  66. package/tsconfig.build.json +24 -0
  67. package/tsconfig.json +21 -0
@@ -0,0 +1,140 @@
1
+ import { number } from "../questions"
2
+ import { success, stringifyToDotEnv } from "../utils"
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import yaml from "yaml"
6
+ import { getAppService } from "./utils"
7
+ const randomString = require("randomstring")
8
+
9
+ const SINGLE_IMAGE = "budibase/budibase:latest"
10
+ const VOL_NAME = "budibase_data"
11
+ export const COMPOSE_PATH = path.resolve("./docker-compose.yaml")
12
+ export const ENV_PATH = path.resolve("./.env")
13
+
14
+ function getSecrets(opts = { single: false }) {
15
+ const secrets = [
16
+ "API_ENCRYPTION_KEY",
17
+ "JWT_SECRET",
18
+ "MINIO_ACCESS_KEY",
19
+ "MINIO_SECRET_KEY",
20
+ "REDIS_PASSWORD",
21
+ "INTERNAL_API_KEY",
22
+ ]
23
+ const obj: Record<string, string> = {}
24
+ secrets.forEach(secret => (obj[secret] = randomString.generate()))
25
+ // setup couch creds separately
26
+ if (opts && opts.single) {
27
+ obj["COUCHDB_USER"] = "admin"
28
+ obj["COUCHDB_PASSWORD"] = randomString.generate()
29
+ } else {
30
+ obj["COUCH_DB_USER"] = "admin"
31
+ obj["COUCH_DB_PASSWORD"] = randomString.generate()
32
+ }
33
+ return obj
34
+ }
35
+
36
+ function getSingleCompose(port: number) {
37
+ const singleComposeObj = {
38
+ version: "3",
39
+ services: {
40
+ budibase: {
41
+ restart: "unless-stopped",
42
+ image: SINGLE_IMAGE,
43
+ ports: [`${port}:80`],
44
+ environment: getSecrets({ single: true }),
45
+ volumes: [`${VOL_NAME}:/data`],
46
+ },
47
+ },
48
+ volumes: {
49
+ [VOL_NAME]: {
50
+ driver: "local",
51
+ },
52
+ },
53
+ }
54
+ return yaml.stringify(singleComposeObj)
55
+ }
56
+
57
+ function getEnv(port: number) {
58
+ const partOne = stringifyToDotEnv({
59
+ MAIN_PORT: port,
60
+ })
61
+ const partTwo = stringifyToDotEnv(getSecrets())
62
+ const partThree = stringifyToDotEnv({
63
+ APP_PORT: 4002,
64
+ WORKER_PORT: 4003,
65
+ MINIO_PORT: 4004,
66
+ COUCH_DB_PORT: 4005,
67
+ REDIS_PORT: 6379,
68
+ WATCHTOWER_PORT: 6161,
69
+ BUDIBASE_ENVIRONMENT: "PRODUCTION",
70
+ })
71
+ return [
72
+ "# Use the main port in the builder for your self hosting URL, e.g. localhost:10000",
73
+ partOne,
74
+ "# This section contains all secrets pertaining to the system",
75
+ partTwo,
76
+ "# This section contains variables that do not need to be altered under normal circumstances",
77
+ partThree,
78
+ ].join("\n")
79
+ }
80
+
81
+ export const ConfigMap = {
82
+ MAIN_PORT: "port",
83
+ }
84
+
85
+ export const QUICK_CONFIG = {
86
+ key: "budibase",
87
+ port: 10000,
88
+ }
89
+
90
+ async function make(
91
+ path: string,
92
+ contentsFn: Function,
93
+ inputs: any = {},
94
+ silent: boolean
95
+ ) {
96
+ const port =
97
+ inputs.port ||
98
+ (await number(
99
+ "Please enter the port on which you want your installation to run: ",
100
+ 10000
101
+ ))
102
+ const fileContents = contentsFn(port)
103
+ fs.writeFileSync(path, fileContents)
104
+ if (!silent) {
105
+ console.log(
106
+ success(
107
+ `Configuration has been written successfully - please check ${path} for more details.`
108
+ )
109
+ )
110
+ }
111
+ }
112
+
113
+ export async function makeEnv(inputs: any = {}, silent: boolean) {
114
+ return make(ENV_PATH, getEnv, inputs, silent)
115
+ }
116
+
117
+ export async function makeSingleCompose(inputs: any = {}, silent: boolean) {
118
+ return make(COMPOSE_PATH, getSingleCompose, inputs, silent)
119
+ }
120
+
121
+ export function getEnvProperty(property: string) {
122
+ const props = fs.readFileSync(ENV_PATH, "utf8").split(property)
123
+ if (props[0].charAt(0) === "=") {
124
+ property = props[0]
125
+ } else {
126
+ property = props[1]
127
+ }
128
+ return property.split("=")[1].split("\n")[0]
129
+ }
130
+
131
+ export function getComposeProperty(property: string) {
132
+ const { service } = getAppService(COMPOSE_PATH)
133
+ if (property === "port" && Array.isArray(service.ports)) {
134
+ const port = service.ports[0]
135
+ return port.split(":")[0]
136
+ } else if (service.environment) {
137
+ return service.environment[property]
138
+ }
139
+ return null
140
+ }
@@ -0,0 +1,30 @@
1
+ import { checkDockerConfigured, checkInitComplete, handleError } from "./utils"
2
+ import { info, success } from "../utils"
3
+ import * as makeFiles from "./makeFiles"
4
+ import compose from "docker-compose"
5
+ import fs from "fs"
6
+
7
+ export async function start() {
8
+ await checkDockerConfigured()
9
+ checkInitComplete()
10
+ console.log(
11
+ info(
12
+ "Starting services, this may take a moment - first time this may take a few minutes to download images."
13
+ )
14
+ )
15
+ let port
16
+ if (fs.existsSync(makeFiles.ENV_PATH)) {
17
+ port = makeFiles.getEnvProperty("MAIN_PORT")
18
+ } else {
19
+ port = makeFiles.getComposeProperty("port")
20
+ }
21
+ await handleError(async () => {
22
+ // need to log as it makes it more clear
23
+ await compose.upAll({ cwd: "./", log: true })
24
+ })
25
+ console.log(
26
+ success(
27
+ `Services started, please go to http://localhost:${port} for next steps.`
28
+ )
29
+ )
30
+ }
@@ -0,0 +1,13 @@
1
+ import { checkDockerConfigured, checkInitComplete, handleError } from "./utils"
2
+ import { info } from "../utils"
3
+ import compose from "docker-compose"
4
+
5
+ export async function status() {
6
+ await checkDockerConfigured()
7
+ checkInitComplete()
8
+ console.log(info("Budibase status"))
9
+ await handleError(async () => {
10
+ const response = await compose.ps()
11
+ console.log(response.out)
12
+ })
13
+ }
@@ -0,0 +1,13 @@
1
+ import { checkDockerConfigured, checkInitComplete, handleError } from "./utils"
2
+ import { info, success } from "../utils"
3
+ import compose from "docker-compose"
4
+
5
+ export async function stop() {
6
+ await checkDockerConfigured()
7
+ checkInitComplete()
8
+ console.log(info("Stopping services, this may take a moment."))
9
+ await handleError(async () => {
10
+ await compose.stop()
11
+ })
12
+ console.log(success("Services have been stopped successfully."))
13
+ }
@@ -0,0 +1,4 @@
1
+ export interface DockerCompose {
2
+ environment: Record<string, string>
3
+ volumes: string[]
4
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ checkDockerConfigured,
3
+ checkInitComplete,
4
+ downloadDockerCompose,
5
+ handleError,
6
+ getServices,
7
+ getServiceImage,
8
+ setServiceImage,
9
+ } from "./utils"
10
+ import { confirmation } from "../questions"
11
+ import compose from "docker-compose"
12
+ import { COMPOSE_PATH } from "./makeFiles"
13
+ import { info, success } from "../utils"
14
+ import { start } from "./start"
15
+
16
+ const BB_COMPOSE_SERVICES = ["app-service", "worker-service", "proxy-service"]
17
+ const BB_SINGLE_SERVICE = ["budibase"]
18
+
19
+ export async function update() {
20
+ const { services } = getServices(COMPOSE_PATH)
21
+ const isSingle = Object.keys(services).length === 1
22
+ await checkDockerConfigured()
23
+ checkInitComplete()
24
+ if (
25
+ !isSingle &&
26
+ (await confirmation("Do you wish to update you docker-compose.yaml?"))
27
+ ) {
28
+ // get current MinIO image
29
+ const image = await getServiceImage("minio")
30
+ await downloadDockerCompose()
31
+ // replace MinIO image
32
+ setServiceImage("minio", image)
33
+ }
34
+ await handleError(async () => {
35
+ const status = await compose.ps()
36
+ const parts = status.out.split("\n")
37
+ const isUp = parts[2] && parts[2].indexOf("Up") !== -1
38
+ if (isUp) {
39
+ console.log(info("Stopping services, this may take a moment."))
40
+ await compose.stop()
41
+ }
42
+ console.log(info("Beginning update, this may take a few minutes."))
43
+ let services
44
+ if (isSingle) {
45
+ services = BB_SINGLE_SERVICE
46
+ } else {
47
+ services = BB_COMPOSE_SERVICES
48
+ }
49
+ await compose.pullMany(services, { log: true })
50
+ if (isUp) {
51
+ console.log(success("Update complete, restarting services..."))
52
+ await start()
53
+ }
54
+ })
55
+ }
@@ -0,0 +1,127 @@
1
+ import { lookpath } from "lookpath"
2
+ import fs from "fs"
3
+ import * as makeFiles from "./makeFiles"
4
+ import { logErrorToFile, downloadFile, error } from "../utils"
5
+ import yaml from "yaml"
6
+ import { DockerCompose } from "./types"
7
+
8
+ const ERROR_FILE = "docker-error.log"
9
+ const COMPOSE_URL =
10
+ "https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml"
11
+
12
+ function composeFilename() {
13
+ return COMPOSE_URL.split("/").slice(-1)[0]
14
+ }
15
+
16
+ export function getServiceImage(service: string) {
17
+ const filename = composeFilename()
18
+ try {
19
+ const { services } = getServices(filename)
20
+ const serviceKey = Object.keys(services).find(name =>
21
+ name.includes(service)
22
+ )
23
+ if (serviceKey) {
24
+ return services[serviceKey].image
25
+ } else {
26
+ return null
27
+ }
28
+ } catch (err) {
29
+ return null
30
+ }
31
+ }
32
+
33
+ export function setServiceImage(service: string, image: string) {
34
+ const filename = composeFilename()
35
+ if (!fs.existsSync(filename)) {
36
+ throw new Error(
37
+ `File ${filename} not found, cannot update ${service} image.`
38
+ )
39
+ }
40
+ const current = getServiceImage(service)!
41
+ let contents = fs.readFileSync(filename, "utf8")
42
+ contents = contents.replace(`image: ${current}`, `image: ${image}`)
43
+ fs.writeFileSync(filename, contents)
44
+ }
45
+
46
+ export async function downloadDockerCompose() {
47
+ const filename = composeFilename()
48
+ try {
49
+ await downloadFile(COMPOSE_URL, `./${filename}`)
50
+ } catch (err) {
51
+ console.error(error(`Failed to retrieve compose file - ${err}`))
52
+ }
53
+ }
54
+
55
+ export async function checkDockerConfigured() {
56
+ const error =
57
+ "docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/docs/docker-compose"
58
+ const docker = await lookpath("docker")
59
+ const compose = await lookpath("docker-compose")
60
+ if (!docker || !compose) {
61
+ throw error
62
+ }
63
+ }
64
+
65
+ export function checkInitComplete() {
66
+ if (
67
+ !fs.existsSync(makeFiles.ENV_PATH) &&
68
+ !fs.existsSync(makeFiles.COMPOSE_PATH)
69
+ ) {
70
+ throw "Please run the hosting --init command before any other hosting command."
71
+ }
72
+ }
73
+
74
+ export async function handleError(func: Function) {
75
+ try {
76
+ await func()
77
+ } catch (err: any) {
78
+ if (err && err.err) {
79
+ logErrorToFile(ERROR_FILE, err.err)
80
+ }
81
+ throw `Failed to start - logs written to file: ${ERROR_FILE}`
82
+ }
83
+ }
84
+
85
+ export function getServices(path: string) {
86
+ if (!fs.existsSync(path)) {
87
+ throw new Error(`No yaml found at path: ${path}`)
88
+ }
89
+ const dockerYaml = fs.readFileSync(path, "utf8")
90
+ const parsedYaml = yaml.parse(dockerYaml)
91
+ return { yaml: parsedYaml, services: parsedYaml.services }
92
+ }
93
+
94
+ export function getAppService(path: string) {
95
+ const { yaml, services } = getServices(path),
96
+ serviceList = Object.keys(services)
97
+ let service
98
+ if (services["app-service"]) {
99
+ service = services["app-service"]
100
+ } else if (serviceList.length === 1) {
101
+ service = services[serviceList[0]]
102
+ }
103
+ return { yaml, service }
104
+ }
105
+
106
+ export function updateDockerComposeService(
107
+ // eslint-disable-next-line no-unused-vars
108
+ updateFn: (service: DockerCompose) => void
109
+ ) {
110
+ const opts = ["docker-compose.yaml", "docker-compose.yml"]
111
+ const dockerFilePath = opts.find(name => fs.existsSync(name))
112
+ if (!dockerFilePath) {
113
+ console.log(error("Unable to locate docker-compose YAML."))
114
+ return
115
+ }
116
+ const { yaml: parsedYaml, service } = getAppService(dockerFilePath)
117
+ if (!service) {
118
+ console.log(
119
+ error(
120
+ "Unable to locate service within compose file, is it a valid Budibase configuration?"
121
+ )
122
+ )
123
+ return
124
+ }
125
+ updateFn(service)
126
+ fs.writeFileSync(dockerFilePath, yaml.stringify(parsedYaml))
127
+ }
@@ -0,0 +1,37 @@
1
+ import { resolve } from "path"
2
+ import fs from "fs"
3
+ import { error, success } from "../utils"
4
+ import { updateDockerComposeService } from "./utils"
5
+ import { DockerCompose } from "./types"
6
+
7
+ export async function watchPlugins(pluginPath: string, silent: boolean) {
8
+ const PLUGIN_PATH = "/plugins"
9
+ // get absolute path
10
+ pluginPath = resolve(pluginPath)
11
+ if (!fs.existsSync(pluginPath)) {
12
+ console.log(
13
+ error(
14
+ `The directory "${pluginPath}" does not exist, please create and then try again.`
15
+ )
16
+ )
17
+ return
18
+ }
19
+ updateDockerComposeService((service: DockerCompose) => {
20
+ // set environment variable
21
+ service.environment["PLUGINS_DIR"] = PLUGIN_PATH
22
+ // add volumes to parsed yaml
23
+ if (!service.volumes) {
24
+ service.volumes = []
25
+ }
26
+ const found = service.volumes.find(vol => vol.includes(PLUGIN_PATH))
27
+ if (found) {
28
+ service.volumes.splice(service.volumes.indexOf(found), 1)
29
+ }
30
+ service.volumes.push(`${pluginPath}:${PLUGIN_PATH}`)
31
+ })
32
+ if (!silent) {
33
+ console.log(
34
+ success(`Docker compose configured to watch directory: ${pluginPath}`)
35
+ )
36
+ }
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { logging } from "@budibase/backend-core"
3
+ logging.disableLogger()
4
+ import "./prebuilds"
5
+ import "./environment"
6
+ import { getCommands } from "./options"
7
+ import { Command } from "commander"
8
+ import { getHelpDescription } from "./utils"
9
+ const json = require("../package.json")
10
+
11
+ // add hosting config
12
+ async function init() {
13
+ const program = new Command()
14
+ .addHelpCommand("help", getHelpDescription("Help with Budibase commands."))
15
+ .helpOption(false)
16
+ .version(json.version)
17
+ // add commands
18
+ for (let command of getCommands()) {
19
+ command.configure(program)
20
+ }
21
+ // this will stop the program if no command found
22
+ await program.parseAsync(process.argv)
23
+ }
24
+
25
+ init().catch(err => {
26
+ console.error(`Unexpected error - `, err)
27
+ })
package/src/options.ts ADDED
@@ -0,0 +1,8 @@
1
+ import analytics from "./analytics"
2
+ import hosting from "./hosting"
3
+ import backups from "./backups"
4
+ import plugins from "./plugins"
5
+
6
+ export function getCommands() {
7
+ return [hosting, analytics, backups, plugins]
8
+ }
@@ -0,0 +1,198 @@
1
+ import { Command } from "../structures/Command"
2
+ import { CommandWord, AnalyticsEvent, InitType } from "../constants"
3
+ import { getSkeleton, fleshOutSkeleton } from "./skeleton"
4
+ import * as questions from "../questions"
5
+ import fs from "fs"
6
+ import { PluginType, PLUGIN_TYPE_ARR } from "@budibase/types"
7
+ import { plugins } from "@budibase/backend-core"
8
+ import { runPkgCommand } from "../exec"
9
+ import { join } from "path"
10
+ import { success, error, info, moveDirectory } from "../utils"
11
+ import { captureEvent } from "../events"
12
+ import { GENERATED_USER_EMAIL } from "../constants"
13
+ import { init as hostingInit } from "../hosting/init"
14
+ import { start as hostingStart } from "../hosting/start"
15
+ const fp = require("find-free-port")
16
+
17
+ type PluginOpts = {
18
+ init?: PluginType
19
+ }
20
+
21
+ function checkInPlugin() {
22
+ if (!fs.existsSync("package.json")) {
23
+ throw new Error(
24
+ "Please run in a plugin directory - must contain package.json"
25
+ )
26
+ }
27
+ if (!fs.existsSync("schema.json")) {
28
+ throw new Error(
29
+ "Please run in a plugin directory - must contain schema.json"
30
+ )
31
+ }
32
+ }
33
+
34
+ async function askAboutTopLevel(name: string) {
35
+ const files = fs.readdirSync(process.cwd())
36
+ // we are in an empty git repo, don't ask
37
+ if (files.find(file => file === ".git")) {
38
+ return false
39
+ } else {
40
+ console.log(
41
+ info(`By default the plugin will be created in the directory "${name}"`)
42
+ )
43
+ console.log(
44
+ info(
45
+ "if you are already in an empty directory, such as a new Git repo, you can disable this functionality."
46
+ )
47
+ )
48
+ return questions.confirmation("Create top level directory?")
49
+ }
50
+ }
51
+
52
+ async function init(opts: PluginOpts) {
53
+ const type = opts["init"] || (opts as PluginType)
54
+ if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
55
+ console.log(
56
+ error(
57
+ "Please provide a type to init, either 'component', 'datasource' or 'automation'."
58
+ )
59
+ )
60
+ return
61
+ }
62
+ console.log(info("Lets get some details about your new plugin:"))
63
+ const name = await questions.string("Name", `budibase-${type}`)
64
+ if (fs.existsSync(name)) {
65
+ console.log(
66
+ error("Directory by plugin name already exists, pick a new name.")
67
+ )
68
+ return
69
+ }
70
+ const description = await questions.string(
71
+ "Description",
72
+ `An amazing Budibase ${type}!`
73
+ )
74
+ const version = await questions.string("Version", "1.0.0")
75
+ const topLevel = await askAboutTopLevel(name)
76
+ // get the skeleton
77
+ console.log(info("Retrieving project..."))
78
+ await getSkeleton(type, name)
79
+ await fleshOutSkeleton(type, name, description, version)
80
+ console.log(info("Installing dependencies..."))
81
+ await runPkgCommand("install", join(process.cwd(), name))
82
+ // if no parent directory desired move to cwd
83
+ if (!topLevel) {
84
+ moveDirectory(name, process.cwd())
85
+ console.log(info(`Plugin created in current directory.`))
86
+ } else {
87
+ console.log(info(`Plugin created in directory "${name}"`))
88
+ }
89
+ captureEvent(AnalyticsEvent.PluginInit, {
90
+ type,
91
+ name,
92
+ description,
93
+ version,
94
+ })
95
+ }
96
+
97
+ async function verify() {
98
+ // will throw errors if not acceptable
99
+ checkInPlugin()
100
+ console.log(info("Verifying plugin..."))
101
+ const schema = fs.readFileSync("schema.json", "utf8")
102
+ const pkg = fs.readFileSync("package.json", "utf8")
103
+ let name, version
104
+ try {
105
+ const schemaJson = JSON.parse(schema)
106
+ const pkgJson = JSON.parse(pkg)
107
+ if (!pkgJson.name || !pkgJson.version || !pkgJson.description) {
108
+ throw new Error(
109
+ "package.json is missing one of 'name', 'version' or 'description'."
110
+ )
111
+ }
112
+ name = pkgJson.name
113
+ version = pkgJson.version
114
+ plugins.validate(schemaJson)
115
+ return { name, version }
116
+ } catch (err: any) {
117
+ if (err && err.message && err.message.includes("not valid JSON")) {
118
+ console.log(error(`schema.json is not valid JSON: ${err.message}`))
119
+ } else {
120
+ console.log(error(`Invalid schema/package.json: ${err.message}`))
121
+ }
122
+ }
123
+ }
124
+
125
+ async function build() {
126
+ const verified = await verify()
127
+ if (!verified?.name) {
128
+ return
129
+ }
130
+ console.log(success("Verified!"))
131
+ console.log(info("Building plugin..."))
132
+ await runPkgCommand("build")
133
+ const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
134
+ console.log(success(`Build complete - output in: ${output}`))
135
+ }
136
+
137
+ async function watch() {
138
+ const verified = await verify()
139
+ if (!verified?.name) {
140
+ return
141
+ }
142
+ const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
143
+ console.log(info(`Watching - build in: ${output}`))
144
+ try {
145
+ await runPkgCommand("watch")
146
+ } catch (err) {
147
+ // always errors when user escapes
148
+ console.log(success("Watch exited."))
149
+ }
150
+ }
151
+
152
+ async function dev() {
153
+ const pluginDir = await questions.string("Directory to watch", "./")
154
+ const [port] = await fp(10000)
155
+ const password = "admin"
156
+ await hostingInit({
157
+ init: InitType.QUICK,
158
+ single: true,
159
+ watchPluginDir: pluginDir,
160
+ genUser: password,
161
+ port,
162
+ silent: true,
163
+ })
164
+ await hostingStart()
165
+ console.log(success(`Configuration has been written to docker-compose.yaml`))
166
+ console.log(
167
+ success("Development environment started successfully - connect at: ") +
168
+ info(`http://localhost:${port}`)
169
+ )
170
+ console.log(success("Use the following credentials to login:"))
171
+ console.log(success("Email: ") + info(GENERATED_USER_EMAIL))
172
+ console.log(success("Password: ") + info(password))
173
+ }
174
+
175
+ export default new Command(`${CommandWord.PLUGIN}`)
176
+ .addHelp(
177
+ "Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
178
+ )
179
+ .addSubOption(
180
+ "--init [type]",
181
+ "Init a new plugin project, with a type of either component or datasource.",
182
+ init
183
+ )
184
+ .addSubOption(
185
+ "--build",
186
+ "Build your plugin, this will verify and produce a final tarball for your project.",
187
+ build
188
+ )
189
+ .addSubOption(
190
+ "--watch",
191
+ "Automatically build any changes to your plugin.",
192
+ watch
193
+ )
194
+ .addSubOption(
195
+ "--dev",
196
+ "Run a development environment which automatically watches the current directory.",
197
+ dev
198
+ )