@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.
- package/.eslintrc +12 -0
- package/LICENSE.md +674 -0
- package/dist/analytics/Client.js +32 -0
- package/dist/analytics/index.js +55 -0
- package/dist/backups/index.js +119 -0
- package/dist/backups/objectStore.js +82 -0
- package/dist/backups/utils.js +110 -0
- package/dist/constants.js +9 -0
- package/dist/core/db.js +55 -0
- package/dist/environment.js +4 -0
- package/dist/events.js +13 -0
- package/dist/exec.js +49 -0
- package/dist/hosting/genUser.js +32 -0
- package/dist/hosting/index.js +21 -0
- package/dist/hosting/init.js +117 -0
- package/dist/hosting/makeFiles.js +144 -0
- package/dist/hosting/start.js +63 -0
- package/dist/hosting/status.js +30 -0
- package/dist/hosting/stop.js +30 -0
- package/dist/hosting/types.js +2 -0
- package/dist/hosting/update.js +62 -0
- package/dist/hosting/utils.js +159 -0
- package/dist/hosting/watch.js +47 -0
- package/dist/index.js +38 -0
- package/dist/options.js +14 -0
- package/dist/plugins/index.js +199 -0
- package/dist/plugins/skeleton.js +75 -0
- package/dist/prebuilds.js +46 -0
- package/dist/questions.js +58 -0
- package/dist/structures/Command.js +75 -0
- package/dist/structures/ConfigManager.js +39 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils.js +126 -0
- package/package.json +62 -0
- package/src/analytics/Client.ts +33 -0
- package/src/analytics/index.ts +60 -0
- package/src/backups/index.ts +130 -0
- package/src/backups/objectStore.ts +69 -0
- package/src/backups/utils.ts +99 -0
- package/src/constants.ts +4 -0
- package/src/core/db.ts +39 -0
- package/src/environment.ts +3 -0
- package/src/events.ts +11 -0
- package/src/exec.ts +27 -0
- package/src/hosting/genUser.ts +23 -0
- package/src/hosting/index.ts +48 -0
- package/src/hosting/init.ts +76 -0
- package/src/hosting/makeFiles.ts +140 -0
- package/src/hosting/start.ts +30 -0
- package/src/hosting/status.ts +13 -0
- package/src/hosting/stop.ts +13 -0
- package/src/hosting/types.ts +4 -0
- package/src/hosting/update.ts +55 -0
- package/src/hosting/utils.ts +127 -0
- package/src/hosting/watch.ts +37 -0
- package/src/index.ts +27 -0
- package/src/options.ts +8 -0
- package/src/plugins/index.ts +198 -0
- package/src/plugins/skeleton.ts +65 -0
- package/src/prebuilds.ts +50 -0
- package/src/questions.ts +40 -0
- package/src/structures/Command.ts +94 -0
- package/src/structures/ConfigManager.ts +49 -0
- package/src/utils.ts +112 -0
- package/start.sh +3 -0
- package/tsconfig.build.json +24 -0
- 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,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,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
|
+
)
|