Merge pull request #9872 from Budibase/labday/cli-ts

Typescript conversion for CLI
This commit is contained in:
Michael Drury 2023-03-03 13:06:43 +00:00 committed by GitHub
commit 271124b47d
49 changed files with 3562 additions and 497 deletions

View File

@ -22,7 +22,7 @@
"test:watch": "jest --watchAll"
},
"dependencies": {
"@budibase/nano": "10.1.1",
"@budibase/nano": "10.1.2",
"@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/types": "2.3.18-alpha.29",
"@shopify/jest-koa-mocks": "5.0.1",

View File

@ -28,6 +28,7 @@ import * as events from "../events"
import * as configs from "../configs"
import { clearCookie, getCookie } from "../utils"
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
import env from "../environment"
const refresh = require("passport-oauth2-refresh")
export {
@ -52,7 +53,7 @@ export const jwt = require("jsonwebtoken")
_passport.use(new LocalStrategy(local.options, local.authenticate))
if (jwtPassport.options.secretOrKey) {
_passport.use(new JwtStrategy(jwtPassport.options, jwtPassport.authenticate))
} else {
} else if (!env.DISABLE_JWT_WARNING) {
logAlert("No JWT Secret supplied, cannot configure JWT strategy")
}

View File

@ -42,7 +42,9 @@ export async function getConfig<T extends Config>(
}
}
export async function save(config: Config) {
export async function save(
config: Config
): Promise<{ id: string; rev: string }> {
const db = context.getGlobalDB()
return db.put(config)
}

View File

@ -94,6 +94,7 @@ const environment = {
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: parseInt(process.env.SMTP_PORT || ""),
SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS,
DISABLE_JWT_WARNING: process.env.DISABLE_JWT_WARNING,
/**
* Enable to allow an admin user to login using a password.
* This can be useful to prevent lockout when configuring SSO.

View File

@ -475,10 +475,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/nano@10.1.1":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.1.tgz#36ccda4d9bb64b5ee14dd2b27a295b40739b1038"
integrity sha512-kbMIzMkjVtl+xI0UPwVU0/pn8/ccxTyfzwBz6Z+ZiN2oUSb0fJCe0qwA6o8dxwSa8nZu4MbGAeMJl3CJndmWtA==
"@budibase/nano@10.1.2":
version "10.1.2"
resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.2.tgz#10fae5a1ab39be6a81261f40e7b7ec6d21cbdd4a"
integrity sha512-1w+YN2n/M5aZ9hBKCP4NEjdQbT8BfCLRizkdvm0Je665eEHw3aE1hvo8mon9Ro9QuDdxj1DfDMMFnym6/QUwpQ==
dependencies:
"@types/tough-cookie" "^4.0.2"
axios "^1.1.3"

View File

@ -6,3 +6,4 @@ docker-error.log
envoy.yaml
*.tar.gz
prebuilds/
dist/

View File

@ -2,15 +2,18 @@
"name": "@budibase/cli",
"version": "2.3.18-alpha.29",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"main": "dist/index.js",
"bin": {
"budi": "src/index.js"
"budi": "dist/index.js"
},
"author": "Budibase",
"license": "GPL-3.0",
"scripts": {
"prebuild": "rm -rf prebuilds 2> /dev/null && cp -r node_modules/leveldown/prebuilds prebuilds",
"build": "yarn prebuild && renamer --find .node --replace .fake 'prebuilds/**' && pkg . --out-path build && yarn postbuild",
"rename": "renamer --find .node --replace .fake 'prebuilds/**'",
"tsc": "tsc -p tsconfig.build.json",
"pkg": "pkg . --out-path build --no-bytecode --public --public-packages \"*\" -C GZip",
"build": "yarn prebuild && yarn rename && yarn tsc && yarn pkg && yarn postbuild",
"postbuild": "rm -rf prebuilds 2> /dev/null"
},
"pkg": {
@ -29,7 +32,6 @@
"@budibase/backend-core": "2.3.18-alpha.29",
"@budibase/string-templates": "2.3.18-alpha.29",
"@budibase/types": "2.3.18-alpha.29",
"axios": "0.21.2",
"chalk": "4.1.0",
"cli-progress": "3.11.2",
"commander": "7.1.0",
@ -40,7 +42,7 @@
"inquirer": "8.0.0",
"joi": "17.6.0",
"lookpath": "1.1.0",
"node-fetch": "2",
"node-fetch": "2.6.7",
"pkg": "5.8.0",
"posthog-node": "1.0.7",
"pouchdb": "7.3.0",
@ -50,8 +52,15 @@
"yaml": "^2.1.1"
},
"devDependencies": {
"@swc/core": "^1.3.25",
"@swc/jest": "^0.2.24",
"@types/jest": "^29.4.0",
"@types/pouchdb": "^6.4.0",
"@types/node-fetch": "2.6.1",
"copyfiles": "^2.4.1",
"eslint": "^7.20.0",
"renamer": "^4.0.0"
"renamer": "^4.0.0",
"ts-node": "^10.9.1",
"typescript": "4.7.3"
}
}

View File

@ -1,32 +0,0 @@
const PostHog = require("posthog-node")
const { POSTHOG_TOKEN, AnalyticsEvents } = require("../constants")
const ConfigManager = require("../structures/ConfigManager")
class AnalyticsClient {
constructor() {
this.client = new PostHog(POSTHOG_TOKEN)
this.configManager = new ConfigManager()
}
capture(event) {
if (this.configManager.config.analyticsDisabled) return
this.client.capture(event)
}
enable() {
this.configManager.removeKey("analyticsDisabled")
this.client.capture({ event: AnalyticsEvents.OptIn, distinctId: "cli" })
}
disable() {
this.client.capture({ event: AnalyticsEvents.OptOut, distinctId: "cli" })
this.configManager.setValue("analyticsDisabled", true)
}
status() {
return this.configManager.config.analyticsDisabled ? "disabled" : "enabled"
}
}
module.exports = AnalyticsClient

View File

@ -0,0 +1,33 @@
import PostHog from "posthog-node"
import { POSTHOG_TOKEN, AnalyticsEvent } from "../constants"
import { ConfigManager } from "../structures/ConfigManager"
export class AnalyticsClient {
client: PostHog
configManager: ConfigManager
constructor() {
this.client = new PostHog(POSTHOG_TOKEN, {})
this.configManager = new ConfigManager()
}
capture(event: { distinctId: string; event: string; properties?: any }) {
if (this.configManager.config.analyticsDisabled) return
this.client.capture(event)
}
enable() {
this.configManager.removeKey("analyticsDisabled")
this.client.capture({ event: AnalyticsEvent.OptIn, distinctId: "cli" })
}
disable() {
this.client.capture({ event: AnalyticsEvent.OptOut, distinctId: "cli" })
this.configManager.setValue("analyticsDisabled", true)
}
status() {
return this.configManager.config.analyticsDisabled ? "disabled" : "enabled"
}
}

View File

@ -1,7 +1,7 @@
const Command = require("../structures/Command")
const { CommandWords } = require("../constants")
const { success, error } = require("../utils")
const AnalyticsClient = require("./Client")
import { Command } from "../structures/Command"
import { CommandWord } from "../constants"
import { success, error } from "../utils"
import { AnalyticsClient } from "./Client"
const client = new AnalyticsClient()
@ -14,11 +14,10 @@ async function optOut() {
"Successfully opted out of Budibase analytics. You can opt in at any time by running 'budi analytics opt-in'"
)
)
} catch (err) {
} catch (err: any) {
console.log(
error(
"Error opting out of Budibase analytics. Please try again later.",
err
`Error opting out of Budibase analytics. Please try again later - ${err}`
)
)
}
@ -50,7 +49,7 @@ async function status() {
}
}
const command = new Command(`${CommandWords.ANALYTICS}`)
export default new Command(`${CommandWord.ANALYTICS}`)
.addHelp("Control the analytics you send to Budibase.")
.addSubOption("--optin", "Opt in to sending analytics to Budibase", optIn)
.addSubOption("--optout", "Opt out of sending analytics to Budibase.", optOut)
@ -59,5 +58,3 @@ const command = new Command(`${CommandWords.ANALYTICS}`)
"Check whether you are currently opted in to Budibase analytics.",
status
)
exports.command = command

View File

@ -1,28 +1,30 @@
const Command = require("../structures/Command")
const { CommandWords } = require("../constants")
const fs = require("fs")
const { join } = require("path")
const { getAllDbs } = require("../core/db")
const tar = require("tar")
const { progressBar, httpCall } = require("../utils")
const {
import { Command } from "../structures/Command"
import { CommandWord } from "../constants"
import fs from "fs"
import { join } from "path"
import { getAllDbs } from "../core/db"
import { progressBar, httpCall } from "../utils"
import {
TEMP_DIR,
COUCH_DIR,
MINIO_DIR,
getConfig,
replication,
getPouches,
} = require("./utils")
const { exportObjects, importObjects } = require("./objectStore")
} from "./utils"
import { exportObjects, importObjects } from "./objectStore"
const tar = require("tar")
async function exportBackup(opts) {
type BackupOpts = { env?: string; import?: string; export?: string }
async function exportBackup(opts: BackupOpts) {
const envFile = opts.env || undefined
let filename = opts["export"] || opts
let filename = opts["export"] || (opts as string)
if (typeof filename !== "string") {
filename = `backup-${new Date().toISOString()}.tar.gz`
}
const config = await getConfig(envFile)
const dbList = await getAllDbs(config["COUCH_DB_URL"])
const dbList = (await getAllDbs(config["COUCH_DB_URL"])) as string[]
const { Remote, Local } = getPouches(config)
if (fs.existsSync(TEMP_DIR)) {
fs.rmSync(TEMP_DIR, { recursive: true })
@ -55,9 +57,9 @@ async function exportBackup(opts) {
console.log(`Generated export file - ${filename}`)
}
async function importBackup(opts) {
async function importBackup(opts: BackupOpts) {
const envFile = opts.env || undefined
const filename = opts["import"] || opts
const filename = opts["import"] || (opts as string)
const config = await getConfig(envFile)
if (!filename || !fs.existsSync(filename)) {
console.error("Cannot import without specifying a valid file to import")
@ -99,7 +101,7 @@ async function importBackup(opts) {
fs.rmSync(TEMP_DIR, { recursive: true })
}
async function pickOne(opts) {
async function pickOne(opts: BackupOpts) {
if (opts["import"]) {
return importBackup(opts)
} else if (opts["export"]) {
@ -107,7 +109,7 @@ async function pickOne(opts) {
}
}
const command = new Command(`${CommandWords.BACKUPS}`)
export default new Command(`${CommandWord.BACKUPS}`)
.addHelp(
"Allows building backups of Budibase, as well as importing a backup to a new instance."
)
@ -126,5 +128,3 @@ const command = new Command(`${CommandWords.BACKUPS}`)
"Provide an environment variable file to configure the CLI.",
pickOne
)
exports.command = command

View File

@ -1,8 +1,8 @@
const { objectStore } = require("@budibase/backend-core")
const fs = require("fs")
const { join } = require("path")
const { TEMP_DIR, MINIO_DIR } = require("./utils")
const { progressBar } = require("../utils")
import { objectStore } from "@budibase/backend-core"
import fs from "fs"
import { join } from "path"
import { TEMP_DIR, MINIO_DIR } from "./utils"
import { progressBar } from "../utils"
const {
ObjectStoreBuckets,
ObjectStore,
@ -13,10 +13,10 @@ const {
const bucketList = Object.values(ObjectStoreBuckets)
exports.exportObjects = async () => {
export async function exportObjects() {
const path = join(TEMP_DIR, MINIO_DIR)
fs.mkdirSync(path)
let fullList = []
let fullList: any[] = []
let errorCount = 0
for (let bucket of bucketList) {
const client = ObjectStore(bucket)
@ -26,7 +26,7 @@ exports.exportObjects = async () => {
errorCount++
continue
}
const list = await client.listObjectsV2().promise()
const list = (await client.listObjectsV2().promise()) as { Contents: any[] }
fullList = fullList.concat(list.Contents.map(el => ({ ...el, bucket })))
}
if (errorCount === bucketList.length) {
@ -48,7 +48,7 @@ exports.exportObjects = async () => {
bar.stop()
}
exports.importObjects = async () => {
export async function importObjects() {
const path = join(TEMP_DIR, MINIO_DIR)
const buckets = fs.readdirSync(path)
let total = 0

View File

@ -1,12 +1,13 @@
const dotenv = require("dotenv")
const fs = require("fs")
const { string } = require("../questions")
const { getPouch } = require("../core/db")
const { env: environment } = require("@budibase/backend-core")
import dotenv from "dotenv"
import fs from "fs"
import { string } from "../questions"
import { getPouch } from "../core/db"
import { env as environment } from "@budibase/backend-core"
import PouchDB from "pouchdb"
exports.TEMP_DIR = ".temp"
exports.COUCH_DIR = "couchdb"
exports.MINIO_DIR = "minio"
export const TEMP_DIR = ".temp"
export const COUCH_DIR = "couchdb"
export const MINIO_DIR = "minio"
const REQUIRED = [
{ value: "MAIN_PORT", default: "10000" },
@ -19,7 +20,7 @@ const REQUIRED = [
{ value: "MINIO_SECRET_KEY" },
]
exports.checkURLs = config => {
export function checkURLs(config: Record<string, string>) {
const mainPort = config["MAIN_PORT"],
username = config["COUCH_DB_USER"],
password = config["COUCH_DB_PASSWORD"]
@ -34,23 +35,23 @@ exports.checkURLs = config => {
return config
}
exports.askQuestions = async () => {
export async function askQuestions() {
console.log(
"*** NOTE: use a .env file to load these parameters repeatedly ***"
)
let config = {}
let config: Record<string, string> = {}
for (let property of REQUIRED) {
config[property.value] = await string(property.value, property.default)
}
return config
}
exports.loadEnvironment = path => {
export function loadEnvironment(path: string) {
if (!fs.existsSync(path)) {
throw "Unable to file specified .env file"
}
const env = fs.readFileSync(path, "utf8")
const config = exports.checkURLs(dotenv.parse(env))
const config = checkURLs(dotenv.parse(env))
for (let required of REQUIRED) {
if (!config[required.value]) {
throw `Cannot find "${required.value}" property in .env file`
@ -60,12 +61,12 @@ exports.loadEnvironment = path => {
}
// true is the default value passed by commander
exports.getConfig = async (envFile = true) => {
export async function getConfig(envFile: boolean | string = true) {
let config
if (envFile !== true) {
config = exports.loadEnvironment(envFile)
config = loadEnvironment(envFile as string)
} else {
config = await exports.askQuestions()
config = await askQuestions()
}
// fill out environment
for (let key of Object.keys(config)) {
@ -74,12 +75,16 @@ exports.getConfig = async (envFile = true) => {
return config
}
exports.replication = async (from, to) => {
export async function replication(
from: PouchDB.Database,
to: PouchDB.Database
) {
const pouch = getPouch()
try {
await pouch.replicate(from, to, {
batch_size: 1000,
batch_limit: 5,
batches_limit: 5,
// @ts-ignore
style: "main_only",
})
} catch (err) {
@ -87,7 +92,7 @@ exports.replication = async (from, to) => {
}
}
exports.getPouches = config => {
export function getPouches(config: Record<string, string>) {
const Remote = getPouch(config["COUCH_DB_URL"])
const Local = getPouch()
return { Remote, Local }

View File

@ -1,25 +0,0 @@
const { Event } = require("@budibase/types")
exports.CommandWords = {
BACKUPS: "backups",
HOSTING: "hosting",
ANALYTICS: "analytics",
HELP: "help",
PLUGIN: "plugins",
}
exports.InitTypes = {
QUICK: "quick",
DIGITAL_OCEAN: "do",
}
exports.AnalyticsEvents = {
OptOut: "analytics:opt:out",
OptIn: "analytics:opt:in",
SelfHostInit: "hosting:init",
PluginInit: Event.PLUGIN_INIT,
}
exports.POSTHOG_TOKEN = "phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS"
exports.GENERATED_USER_EMAIL = "admin@admin.com"

View File

@ -0,0 +1,4 @@
export { CommandWord, InitType, AnalyticsEvent } from "@budibase/types"
export const POSTHOG_TOKEN = "phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS"
export const GENERATED_USER_EMAIL = "admin@admin.com"

View File

@ -1,12 +1,12 @@
const PouchDB = require("pouchdb")
const { checkSlashesInUrl } = require("../utils")
const fetch = require("node-fetch")
import PouchDB from "pouchdb"
import { checkSlashesInUrl } from "../utils"
import fetch from "node-fetch"
/**
* Fully qualified URL including username and password, or nothing for local
*/
exports.getPouch = (url = undefined) => {
let POUCH_DB_DEFAULTS = {}
export function getPouch(url?: string) {
let POUCH_DB_DEFAULTS
if (!url) {
POUCH_DB_DEFAULTS = {
prefix: undefined,
@ -19,11 +19,12 @@ exports.getPouch = (url = undefined) => {
}
const replicationStream = require("pouchdb-replication-stream")
PouchDB.plugin(replicationStream.plugin)
// @ts-ignore
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
return PouchDB.defaults(POUCH_DB_DEFAULTS)
return PouchDB.defaults(POUCH_DB_DEFAULTS) as PouchDB.Static
}
exports.getAllDbs = async url => {
export async function getAllDbs(url: string) {
const response = await fetch(
checkSlashesInUrl(encodeURI(`${url}/_all_dbs`)),
{

View File

@ -1,2 +1,3 @@
process.env.NO_JS = "1"
process.env.JS_BCRYPT = "1"
process.env.DISABLE_JWT_WARNING = "1"

View File

@ -1,11 +0,0 @@
const AnalyticsClient = require("./analytics/Client")
const client = new AnalyticsClient()
exports.captureEvent = (event, properties) => {
client.capture({
distinctId: "cli",
event,
properties,
})
}

View File

@ -0,0 +1,11 @@
import { AnalyticsClient } from "./analytics/Client"
const client = new AnalyticsClient()
export function captureEvent(event: string, properties: any) {
client.capture({
distinctId: "cli",
event,
properties,
})
}

View File

@ -1,21 +1,21 @@
const util = require("util")
const exec = util.promisify(require("child_process").exec)
import util from "util"
const runCommand = util.promisify(require("child_process").exec)
exports.exec = async (command, dir = "./") => {
const { stdout } = await exec(command, { cwd: dir })
export async function exec(command: string, dir = "./") {
const { stdout } = await runCommand(command, { cwd: dir })
return stdout
}
exports.utilityInstalled = async utilName => {
export async function utilityInstalled(utilName: string) {
try {
await exports.exec(`${utilName} --version`)
await exec(`${utilName} --version`)
return true
} catch (err) {
return false
}
}
exports.runPkgCommand = async (command, dir = "./") => {
export async function runPkgCommand(command: string, dir = "./") {
const yarn = await exports.utilityInstalled("yarn")
const npm = await exports.utilityInstalled("npm")
if (!yarn && !npm) {

View File

@ -2,15 +2,16 @@ const { success } = require("../utils")
const { updateDockerComposeService } = require("./utils")
const randomString = require("randomstring")
const { GENERATED_USER_EMAIL } = require("../constants")
import { DockerCompose } from "./types"
exports.generateUser = async (password, silent) => {
export async function generateUser(password: string | null, silent: boolean) {
const email = GENERATED_USER_EMAIL
if (!password) {
password = randomString.generate({ length: 6 })
}
updateDockerComposeService(service => {
updateDockerComposeService((service: DockerCompose) => {
service.environment["BB_ADMIN_USER_EMAIL"] = email
service.environment["BB_ADMIN_USER_PASSWORD"] = password
service.environment["BB_ADMIN_USER_PASSWORD"] = password as string
})
if (!silent) {
console.log(

View File

@ -1,14 +1,14 @@
const Command = require("../structures/Command")
const { CommandWords } = require("../constants")
const { init } = require("./init")
const { start } = require("./start")
const { stop } = require("./stop")
const { status } = require("./status")
const { update } = require("./update")
const { generateUser } = require("./genUser")
const { watchPlugins } = require("./watch")
import { Command } from "../structures/Command"
import { CommandWord } from "../constants"
import { init } from "./init"
import { start } from "./start"
import { stop } from "./stop"
import { status } from "./status"
import { update } from "./update"
import { generateUser } from "./genUser"
import { watchPlugins } from "./watch"
const command = new Command(`${CommandWords.HOSTING}`)
export default new Command(`${CommandWord.HOSTING}`)
.addHelp("Controls self hosting on the Budibase platform.")
.addSubOption(
"--init [type]",
@ -46,5 +46,3 @@ const command = new Command(`${CommandWords.HOSTING}`)
generateUser
)
.addSubOption("--single", "Specify this with init to use the single image.")
exports.command = command

View File

@ -1,24 +1,25 @@
const { InitTypes, AnalyticsEvents } = require("../constants")
const { confirmation } = require("../questions")
const { captureEvent } = require("../events")
const makeFiles = require("./makeFiles")
const axios = require("axios")
const { parseEnv } = require("../utils")
const { checkDockerConfigured, downloadFiles } = require("./utils")
const { watchPlugins } = require("./watch")
const { generateUser } = require("./genUser")
import { InitType, AnalyticsEvent } from "../constants"
import { confirmation } from "../questions"
import { captureEvent } from "../events"
import * as makeFiles from "./makeFiles"
import { parseEnv } from "../utils"
import { checkDockerConfigured, downloadDockerCompose } from "./utils"
import { watchPlugins } from "./watch"
import { generateUser } from "./genUser"
import fetch from "node-fetch"
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
async function getInitConfig(type, isQuick, port) {
const config = isQuick ? makeFiles.QUICK_CONFIG : {}
if (type === InitTypes.DIGITAL_OCEAN) {
async function getInitConfig(type: string, isQuick: boolean, port: number) {
const config: any = isQuick ? makeFiles.QUICK_CONFIG : {}
if (type === InitType.DIGITAL_OCEAN) {
try {
const output = await axios.get(DO_USER_DATA_URL)
const response = parseEnv(output.data)
const output = await fetch(DO_USER_DATA_URL)
const data = await output.text()
const response = parseEnv(data)
for (let [key, value] of Object.entries(makeFiles.ConfigMap)) {
if (response[key]) {
config[value] = response[key]
config[value as string] = response[key]
}
}
} catch (err) {
@ -32,7 +33,7 @@ async function getInitConfig(type, isQuick, port) {
return config
}
exports.init = async opts => {
export async function init(opts: any) {
let type, isSingle, watchDir, genUser, port, silent
if (typeof opts === "string") {
type = opts
@ -44,7 +45,7 @@ exports.init = async opts => {
port = opts["port"]
silent = opts["silent"]
}
const isQuick = type === InitTypes.QUICK || type === InitTypes.DIGITAL_OCEAN
const isQuick = type === InitType.QUICK || type === InitType.DIGITAL_OCEAN
await checkDockerConfigured()
if (!isQuick) {
const shouldContinue = await confirmation(
@ -55,12 +56,12 @@ exports.init = async opts => {
return
}
}
captureEvent(AnalyticsEvents.SelfHostInit, {
captureEvent(AnalyticsEvent.SelfHostInit, {
type,
})
const config = await getInitConfig(type, isQuick, port)
if (!isSingle) {
await downloadFiles()
await downloadDockerCompose()
await makeFiles.makeEnv(config, silent)
} else {
await makeFiles.makeSingleCompose(config, silent)

View File

@ -1,15 +1,15 @@
const { number } = require("../questions")
const { success, stringifyToDotEnv } = require("../utils")
const fs = require("fs")
const path = require("path")
import { number } from "../questions"
import { success, stringifyToDotEnv } from "../utils"
import fs from "fs"
import path from "path"
import yaml from "yaml"
import { getAppService } from "./utils"
const randomString = require("randomstring")
const yaml = require("yaml")
const { getAppService } = require("./utils")
const SINGLE_IMAGE = "budibase/budibase:latest"
const VOL_NAME = "budibase_data"
const COMPOSE_PATH = path.resolve("./docker-compose.yaml")
const ENV_PATH = path.resolve("./.env")
export const COMPOSE_PATH = path.resolve("./docker-compose.yaml")
export const ENV_PATH = path.resolve("./.env")
function getSecrets(opts = { single: false }) {
const secrets = [
@ -19,7 +19,7 @@ function getSecrets(opts = { single: false }) {
"REDIS_PASSWORD",
"INTERNAL_API_KEY",
]
const obj = {}
const obj: Record<string, string> = {}
secrets.forEach(secret => (obj[secret] = randomString.generate()))
// setup couch creds separately
if (opts && opts.single) {
@ -32,7 +32,7 @@ function getSecrets(opts = { single: false }) {
return obj
}
function getSingleCompose(port) {
function getSingleCompose(port: number) {
const singleComposeObj = {
version: "3",
services: {
@ -53,7 +53,7 @@ function getSingleCompose(port) {
return yaml.stringify(singleComposeObj)
}
function getEnv(port) {
function getEnv(port: number) {
const partOne = stringifyToDotEnv({
MAIN_PORT: port,
})
@ -77,19 +77,21 @@ function getEnv(port) {
].join("\n")
}
exports.ENV_PATH = ENV_PATH
exports.COMPOSE_PATH = COMPOSE_PATH
module.exports.ConfigMap = {
export const ConfigMap = {
MAIN_PORT: "port",
}
module.exports.QUICK_CONFIG = {
export const QUICK_CONFIG = {
key: "budibase",
port: 10000,
}
async function make(path, contentsFn, inputs = {}, silent) {
async function make(
path: string,
contentsFn: Function,
inputs: any = {},
silent: boolean
) {
const port =
inputs.port ||
(await number(
@ -107,15 +109,15 @@ async function make(path, contentsFn, inputs = {}, silent) {
}
}
module.exports.makeEnv = async (inputs = {}, silent) => {
export async function makeEnv(inputs: any = {}, silent: boolean) {
return make(ENV_PATH, getEnv, inputs, silent)
}
module.exports.makeSingleCompose = async (inputs = {}, silent) => {
export async function makeSingleCompose(inputs: any = {}, silent: boolean) {
return make(COMPOSE_PATH, getSingleCompose, inputs, silent)
}
module.exports.getEnvProperty = property => {
export function getEnvProperty(property: string) {
const props = fs.readFileSync(ENV_PATH, "utf8").split(property)
if (props[0].charAt(0) === "=") {
property = props[0]
@ -125,7 +127,7 @@ module.exports.getEnvProperty = property => {
return property.split("=")[1].split("\n")[0]
}
module.exports.getComposeProperty = property => {
export function getComposeProperty(property: string) {
const { service } = getAppService(COMPOSE_PATH)
if (property === "port" && Array.isArray(service.ports)) {
const port = service.ports[0]

View File

@ -1,14 +1,10 @@
const {
checkDockerConfigured,
checkInitComplete,
handleError,
} = require("./utils")
const { info, success } = require("../utils")
const makeFiles = require("./makeFiles")
const compose = require("docker-compose")
const fs = require("fs")
import { checkDockerConfigured, checkInitComplete, handleError } from "./utils"
import { info, success } from "../utils"
import * as makeFiles from "./makeFiles"
import compose from "docker-compose"
import fs from "fs"
exports.start = async () => {
export async function start() {
await checkDockerConfigured()
checkInitComplete()
console.log(

View File

@ -1,12 +1,8 @@
const {
checkDockerConfigured,
checkInitComplete,
handleError,
} = require("./utils")
const { info } = require("../utils")
const compose = require("docker-compose")
import { checkDockerConfigured, checkInitComplete, handleError } from "./utils"
import { info } from "../utils"
import compose from "docker-compose"
exports.status = async () => {
export async function status() {
await checkDockerConfigured()
checkInitComplete()
console.log(info("Budibase status"))

View File

@ -1,12 +1,8 @@
const {
checkDockerConfigured,
checkInitComplete,
handleError,
} = require("./utils")
const { info, success } = require("../utils")
const compose = require("docker-compose")
import { checkDockerConfigured, checkInitComplete, handleError } from "./utils"
import { info, success } from "../utils"
import compose from "docker-compose"
exports.stop = async () => {
export async function stop() {
await checkDockerConfigured()
checkInitComplete()
console.log(info("Stopping services, this may take a moment."))

View File

@ -0,0 +1,4 @@
export interface DockerCompose {
environment: Record<string, string>
volumes: string[]
}

View File

@ -1,20 +1,20 @@
const {
import {
checkDockerConfigured,
checkInitComplete,
downloadFiles,
downloadDockerCompose,
handleError,
getServices,
} = require("./utils")
const { confirmation } = require("../questions")
const compose = require("docker-compose")
const { COMPOSE_PATH } = require("./makeFiles")
const { info, success } = require("../utils")
const { start } = require("./start")
} from "./utils"
import { confirmation } from "../questions"
import compose from "docker-compose"
import { COMPOSE_PATH } from "./makeFiles"
import { info, success } from "../utils"
import { start } from "./start"
const BB_COMPOSE_SERVICES = ["app-service", "worker-service", "proxy-service"]
const BB_SINGLE_SERVICE = ["budibase"]
exports.update = async () => {
export async function update() {
const { services } = getServices(COMPOSE_PATH)
const isSingle = Object.keys(services).length === 1
await checkDockerConfigured()
@ -23,7 +23,7 @@ exports.update = async () => {
!isSingle &&
(await confirmation("Do you wish to update you docker-compose.yaml?"))
) {
await downloadFiles()
await downloadDockerCompose()
}
await handleError(async () => {
const status = await compose.ps()

View File

@ -1,24 +1,24 @@
const { lookpath } = require("lookpath")
const fs = require("fs")
const makeFiles = require("./makeFiles")
const { logErrorToFile, downloadFile, error } = require("../utils")
const yaml = require("yaml")
import { lookpath } from "lookpath"
import fs from "fs"
import * as makeFiles from "./makeFiles"
import { logErrorToFile, downloadFile, error } from "../utils"
import yaml from "yaml"
import { DockerCompose } from "./types"
const ERROR_FILE = "docker-error.log"
const FILE_URLS = [
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",
]
const COMPOSE_URL =
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml"
exports.downloadFiles = async () => {
const promises = []
for (let url of FILE_URLS) {
const fileName = url.split("/").slice(-1)[0]
promises.push(downloadFile(url, `./${fileName}`))
export async function downloadDockerCompose() {
const fileName = COMPOSE_URL.split("/").slice(-1)[0]
try {
await downloadFile(COMPOSE_URL, `./${fileName}`)
} catch (err) {
console.error(error(`Failed to retrieve compose file - ${err}`))
}
await Promise.all(promises)
}
exports.checkDockerConfigured = async () => {
export async function checkDockerConfigured() {
const error =
"docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/docs/docker-compose"
const docker = await lookpath("docker")
@ -28,7 +28,7 @@ exports.checkDockerConfigured = async () => {
}
}
exports.checkInitComplete = () => {
export function checkInitComplete() {
if (
!fs.existsSync(makeFiles.ENV_PATH) &&
!fs.existsSync(makeFiles.COMPOSE_PATH)
@ -37,10 +37,10 @@ exports.checkInitComplete = () => {
}
}
exports.handleError = async func => {
export async function handleError(func: Function) {
try {
await func()
} catch (err) {
} catch (err: any) {
if (err && err.err) {
logErrorToFile(ERROR_FILE, err.err)
}
@ -48,14 +48,14 @@ exports.handleError = async func => {
}
}
exports.getServices = path => {
export function getServices(path: string) {
const dockerYaml = fs.readFileSync(path, "utf8")
const parsedYaml = yaml.parse(dockerYaml)
return { yaml: parsedYaml, services: parsedYaml.services }
}
exports.getAppService = path => {
const { yaml, services } = exports.getServices(path),
export function getAppService(path: string) {
const { yaml, services } = getServices(path),
serviceList = Object.keys(services)
let service
if (services["app-service"]) {
@ -66,14 +66,17 @@ exports.getAppService = path => {
return { yaml, service }
}
exports.updateDockerComposeService = updateFn => {
export function updateDockerComposeService(
// eslint-disable-next-line no-unused-vars
updateFn: (service: DockerCompose) => void
) {
const opts = ["docker-compose.yaml", "docker-compose.yml"]
const dockerFilePath = opts.find(name => fs.existsSync(name))
if (!dockerFilePath) {
console.log(error("Unable to locate docker-compose YAML."))
return
}
const { yaml: parsedYaml, service } = exports.getAppService(dockerFilePath)
const { yaml: parsedYaml, service } = getAppService(dockerFilePath)
if (!service) {
console.log(
error(

View File

@ -1,9 +1,10 @@
const { resolve } = require("path")
const fs = require("fs")
const { error, success } = require("../utils")
const { updateDockerComposeService } = require("./utils")
import { resolve } from "path"
import fs from "fs"
import { error, success } from "../utils"
import { updateDockerComposeService } from "./utils"
import { DockerCompose } from "./types"
exports.watchPlugins = async (pluginPath, silent) => {
export async function watchPlugins(pluginPath: string, silent: boolean) {
const PLUGIN_PATH = "/plugins"
// get absolute path
pluginPath = resolve(pluginPath)
@ -15,7 +16,7 @@ exports.watchPlugins = async (pluginPath, silent) => {
)
return
}
updateDockerComposeService(service => {
updateDockerComposeService((service: DockerCompose) => {
// set environment variable
service.environment["PLUGINS_DIR"] = PLUGIN_PATH
// add volumes to parsed yaml

View File

@ -1,10 +1,10 @@
#!/usr/bin/env node
require("./prebuilds")
require("./environment")
import "./prebuilds"
import "./environment"
import { getCommands } from "./options"
import { Command } from "commander"
import { getHelpDescription } from "./utils"
const json = require("../package.json")
const { getCommands } = require("./options")
const { Command } = require("commander")
const { getHelpDescription } = require("./utils")
// add hosting config
async function init() {

View File

@ -1,8 +0,0 @@
const analytics = require("./analytics")
const hosting = require("./hosting")
const backups = require("./backups")
const plugins = require("./plugins")
exports.getCommands = () => {
return [hosting.command, analytics.command, backups.command, plugins.command]
}

View File

@ -0,0 +1,8 @@
import analytics from "./analytics"
import hosting from "./hosting"
import backups from "./backups"
import plugins from "./plugins"
export function getCommands() {
return [hosting, analytics, backups, plugins]
}

View File

@ -1,18 +1,22 @@
const Command = require("../structures/Command")
const { CommandWords, AnalyticsEvents, InitTypes } = require("../constants")
const { getSkeleton, fleshOutSkeleton } = require("./skeleton")
const questions = require("../questions")
const fs = require("fs")
const { PLUGIN_TYPE_ARR } = require("@budibase/types")
const { plugins } = require("@budibase/backend-core")
const { runPkgCommand } = require("../exec")
const { join } = require("path")
const { success, error, info, moveDirectory } = require("../utils")
const { captureEvent } = require("../events")
import { Command } from "../structures/Command"
import { CommandWord, AnalyticsEvent, InitType } from "../constants"
import { getSkeleton, fleshOutSkeleton } from "./skeleton"
import * as questions from "../questions"
import fs from "fs"
import { PluginType, PLUGIN_TYPE_ARR } from "@budibase/types"
import { plugins } from "@budibase/backend-core"
import { runPkgCommand } from "../exec"
import { join } from "path"
import { success, error, info, moveDirectory } from "../utils"
import { captureEvent } from "../events"
import { GENERATED_USER_EMAIL } from "../constants"
import { init as hostingInit } from "../hosting/init"
import { start as hostingStart } from "../hosting/start"
const fp = require("find-free-port")
const { GENERATED_USER_EMAIL } = require("../constants")
const { init: hostingInit } = require("../hosting/init")
const { start: hostingStart } = require("../hosting/start")
type PluginOpts = {
init?: PluginType
}
function checkInPlugin() {
if (!fs.existsSync("package.json")) {
@ -27,7 +31,7 @@ function checkInPlugin() {
}
}
async function askAboutTopLevel(name) {
async function askAboutTopLevel(name: string) {
const files = fs.readdirSync(process.cwd())
// we are in an empty git repo, don't ask
if (files.find(file => file === ".git")) {
@ -45,8 +49,8 @@ async function askAboutTopLevel(name) {
}
}
async function init(opts) {
const type = opts["init"] || opts
async function init(opts: PluginOpts) {
const type = opts["init"] || (opts as PluginType)
if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
console.log(
error(
@ -82,7 +86,7 @@ async function init(opts) {
} else {
console.log(info(`Plugin created in directory "${name}"`))
}
captureEvent(AnalyticsEvents.PluginInit, {
captureEvent(AnalyticsEvent.PluginInit, {
type,
name,
description,
@ -109,7 +113,7 @@ async function verify() {
version = pkgJson.version
plugins.validate(schemaJson)
return { name, version }
} catch (err) {
} catch (err: any) {
if (err && err.message && err.message.includes("not valid JSON")) {
console.log(error(`schema.json is not valid JSON: ${err.message}`))
} else {
@ -120,7 +124,7 @@ async function verify() {
async function build() {
const verified = await verify()
if (!verified.name) {
if (!verified?.name) {
return
}
console.log(success("Verified!"))
@ -132,7 +136,7 @@ async function build() {
async function watch() {
const verified = await verify()
if (!verified.name) {
if (!verified?.name) {
return
}
const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
@ -150,7 +154,7 @@ async function dev() {
const [port] = await fp(10000)
const password = "admin"
await hostingInit({
init: InitTypes.QUICK,
init: InitType.QUICK,
single: true,
watchPluginDir: pluginDir,
genUser: password,
@ -168,7 +172,7 @@ async function dev() {
console.log(success("Password: ") + info(password))
}
const command = new Command(`${CommandWords.PLUGIN}`)
export default new Command(`${CommandWord.PLUGIN}`)
.addHelp(
"Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
)
@ -192,5 +196,3 @@ const command = new Command(`${CommandWords.PLUGIN}`)
"Run a development environment which automatically watches the current directory.",
dev
)
exports.command = command

View File

@ -1,21 +1,21 @@
const fetch = require("node-fetch")
import fetch from "node-fetch"
import fs from "fs"
import os from "os"
import { join } from "path"
import { processStringSync } from "@budibase/string-templates"
const download = require("download")
const fs = require("fs")
const os = require("os")
const { join } = require("path")
const tar = require("tar")
const { processStringSync } = require("@budibase/string-templates")
const HBS_FILES = ["package.json.hbs", "schema.json.hbs", "README.md.hbs"]
async function getSkeletonUrl(type) {
async function getSkeletonUrl(type: string) {
const resp = await fetch(
"https://api.github.com/repos/budibase/budibase-skeleton/releases/latest"
)
if (resp.status >= 300) {
throw new Error("Failed to retrieve skeleton metadata")
}
const json = await resp.json()
const json = (await resp.json()) as { assets: any[] }
for (let asset of json["assets"]) {
if (asset.name && asset.name.includes(type)) {
return asset["browser_download_url"]
@ -24,7 +24,7 @@ async function getSkeletonUrl(type) {
throw new Error("No skeleton found in latest release.")
}
exports.getSkeleton = async (type, name) => {
export async function getSkeleton(type: string, name: string) {
const url = await getSkeletonUrl(type)
const tarballFile = join(os.tmpdir(), "skeleton.tar.gz")
@ -40,7 +40,12 @@ exports.getSkeleton = async (type, name) => {
fs.rmSync(tarballFile)
}
exports.fleshOutSkeleton = async (type, name, description, version) => {
export async function fleshOutSkeleton(
type: string,
name: string,
description: string,
version: string
) {
for (let file of HBS_FILES) {
const oldFile = join(name, file),
newFile = join(name, file.substring(0, file.length - 4))

View File

@ -1,7 +1,8 @@
const os = require("os")
const { join } = require("path")
const fs = require("fs")
const { error } = require("./utils")
import os from "os"
import { join } from "path"
import fs from "fs"
import { error } from "./utils"
const PREBUILDS = "prebuilds"
const ARCH = `${os.platform()}-${os.arch()}`
const PREBUILD_DIR = join(process.execPath, "..", PREBUILDS, ARCH)
@ -26,8 +27,8 @@ function checkForBinaries() {
}
}
function cleanup(evt) {
if (!isNaN(evt)) {
function cleanup(evt?: number) {
if (evt && !isNaN(evt)) {
return
}
if (evt) {

View File

@ -1,6 +1,6 @@
const inquirer = require("inquirer")
exports.confirmation = async question => {
export async function confirmation(question: string) {
const config = {
type: "confirm",
message: question,
@ -10,8 +10,8 @@ exports.confirmation = async question => {
return (await inquirer.prompt(config)).confirmation
}
exports.string = async (question, defaultString = null) => {
const config = {
export async function string(question: string, defaultString?: string) {
const config: any = {
type: "input",
name: "string",
message: question,
@ -22,12 +22,12 @@ exports.string = async (question, defaultString = null) => {
return (await inquirer.prompt(config)).string
}
exports.number = async (question, defaultNumber) => {
const config = {
export async function number(question: string, defaultNumber?: number) {
const config: any = {
type: "input",
name: "number",
message: question,
validate: value => {
validate: (value: string) => {
let valid = !isNaN(parseFloat(value))
return valid || "Please enter a number"
},

View File

@ -1,19 +1,31 @@
const {
import {
getSubHelpDescription,
getHelpDescription,
error,
capitaliseFirstLetter,
} = require("../utils")
} from "../utils"
class Command {
constructor(command, func = null) {
type CommandOpt = {
command: string
help: string
func?: Function
extras: any[]
}
export class Command {
command: string
opts: CommandOpt[]
func?: Function
help?: string
constructor(command: string, func?: Function) {
// if there are options, need to just get the command name
this.command = command
this.opts = []
this.func = func
}
convertToCommander(lookup) {
convertToCommander(lookup: string) {
const parts = lookup.toLowerCase().split("-")
// camel case, separate out first
const first = parts.shift()
@ -22,21 +34,26 @@ class Command {
.join("")
}
addHelp(help) {
addHelp(help: string) {
this.help = help
return this
}
addSubOption(command, help, func, extras = []) {
addSubOption(
command: string,
help: string,
func?: Function,
extras: any[] = []
) {
this.opts.push({ command, help, func, extras })
return this
}
configure(program) {
configure(program: any) {
const thisCmd = this
let command = program.command(thisCmd.command)
if (this.help) {
command = command.description(getHelpDescription(thisCmd.help))
command = command.description(getHelpDescription(thisCmd.help!))
}
for (let opt of thisCmd.opts) {
command = command.option(opt.command, getSubHelpDescription(opt.help))
@ -45,7 +62,7 @@ class Command {
"--help",
getSubHelpDescription(`Get help with ${this.command} options`)
)
command.action(async options => {
command.action(async (options: Record<string, string>) => {
try {
let executed = false,
found = false
@ -53,7 +70,7 @@ class Command {
let lookup = opt.command.split(" ")[0].replace("--", "")
// need to handle how commander converts watch-plugin-dir to watchPluginDir
lookup = this.convertToCommander(lookup)
found = !executed && options[lookup]
found = !executed && !!options[lookup]
if (found && opt.func) {
const input =
Object.keys(options).length > 1 ? options : options[lookup]
@ -69,11 +86,9 @@ class Command {
console.log(error(`Unknown ${this.command} option.`))
command.help()
}
} catch (err) {
} catch (err: any) {
console.log(error(err))
}
})
}
}
module.exports = Command

View File

@ -3,7 +3,9 @@ const path = require("path")
const os = require("os")
const { error } = require("../utils")
class ConfigManager {
export class ConfigManager {
path: string
constructor() {
this.path = path.join(os.homedir(), ".budibase.json")
if (!fs.existsSync(this.path)) {
@ -24,26 +26,24 @@ class ConfigManager {
}
}
set config(json) {
set config(json: any) {
fs.writeFileSync(this.path, JSON.stringify(json))
}
getValue(key) {
getValue(key: string) {
return this.config[key]
}
setValue(key, value) {
setValue(key: string, value: any) {
this.config = {
...this.config,
[key]: value,
}
}
removeKey(key) {
removeKey(key: string) {
const updated = { ...this.config }
delete updated[key]
this.config = updated
}
}
module.exports = ConfigManager

View File

@ -1,106 +0,0 @@
const chalk = require("chalk")
const fs = require("fs")
const axios = require("axios")
const path = require("path")
const progress = require("cli-progress")
const { join } = require("path")
exports.downloadFile = async (url, filePath) => {
filePath = path.resolve(filePath)
const writer = fs.createWriteStream(filePath)
const response = await axios({
url,
method: "GET",
responseType: "stream",
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on("finish", resolve)
writer.on("error", reject)
})
}
exports.httpCall = async (url, method) => {
const response = await axios({
url,
method,
})
return response.data
}
exports.getHelpDescription = string => {
return chalk.cyan(string)
}
exports.getSubHelpDescription = string => {
return chalk.green(string)
}
exports.error = error => {
return chalk.red(`Error - ${error}`)
}
exports.success = success => {
return chalk.green(success)
}
exports.info = info => {
return chalk.cyan(info)
}
exports.logErrorToFile = (file, error) => {
fs.writeFileSync(path.resolve(`./${file}`), `Budibase Error\n${error}`)
}
exports.parseEnv = env => {
const lines = env.toString().split("\n")
let result = {}
for (const line of lines) {
const match = line.match(/^([^=:#]+?)[=:](.*)/)
if (match) {
result[match[1].trim()] = match[2].trim()
}
}
return result
}
exports.progressBar = total => {
const bar = new progress.SingleBar({}, progress.Presets.shades_classic)
bar.start(total, 0)
return bar
}
exports.checkSlashesInUrl = url => {
return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2")
}
exports.moveDirectory = (oldPath, newPath) => {
const files = fs.readdirSync(oldPath)
// check any file exists already
for (let file of files) {
if (fs.existsSync(join(newPath, file))) {
throw new Error(
"Unable to remove top level directory - some skeleton files already exist."
)
}
}
for (let file of files) {
fs.renameSync(join(oldPath, file), join(newPath, file))
}
fs.rmdirSync(oldPath)
}
exports.capitaliseFirstLetter = str => {
return str.charAt(0).toUpperCase() + str.slice(1)
}
exports.stringifyToDotEnv = json => {
let str = ""
for (let [key, value] of Object.entries(json)) {
str += `${key}=${value}\n`
}
return str
}

112
packages/cli/src/utils.ts Normal file
View File

@ -0,0 +1,112 @@
import chalk from "chalk"
import fs from "fs"
import path from "path"
import { join } from "path"
import fetch from "node-fetch"
const progress = require("cli-progress")
export function downloadFile(url: string, filePath: string) {
return new Promise((resolve, reject) => {
filePath = path.resolve(filePath)
fetch(url, {
method: "GET",
})
.then(response => {
const writer = fs.createWriteStream(filePath)
if (response.body) {
response.body.pipe(writer)
response.body.on("end", resolve)
response.body.on("error", reject)
} else {
throw new Error(
`Unable to retrieve docker-compose file - ${response.status}`
)
}
})
.catch(err => {
throw err
})
})
}
export async function httpCall(url: string, method: string) {
const response = await fetch(url, {
method,
})
return response.body
}
export function getHelpDescription(str: string) {
return chalk.cyan(str)
}
export function getSubHelpDescription(str: string) {
return chalk.green(str)
}
export function error(err: string | number) {
process.exitCode = -1
return chalk.red(`Error - ${err}`)
}
export function success(str: string) {
return chalk.green(str)
}
export function info(str: string) {
return chalk.cyan(str)
}
export function logErrorToFile(file: string, error: string) {
fs.writeFileSync(path.resolve(`./${file}`), `Budibase Error\n${error}`)
}
export function parseEnv(env: string) {
const lines = env.toString().split("\n")
let result: Record<string, string> = {}
for (const line of lines) {
const match = line.match(/^([^=:#]+?)[=:](.*)/)
if (match) {
result[match[1].trim()] = match[2].trim()
}
}
return result
}
export function progressBar(total: number) {
const bar = new progress.SingleBar({}, progress.Presets.shades_classic)
bar.start(total, 0)
return bar
}
export function checkSlashesInUrl(url: string) {
return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2")
}
export function moveDirectory(oldPath: string, newPath: string) {
const files = fs.readdirSync(oldPath)
// check any file exists already
for (let file of files) {
if (fs.existsSync(join(newPath, file))) {
throw new Error(
"Unable to remove top level directory - some skeleton files already exist."
)
}
}
for (let file of files) {
fs.renameSync(join(oldPath, file), join(newPath, file))
}
fs.rmdirSync(oldPath)
}
export function capitaliseFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
export function stringifyToDotEnv(json: Record<string, string | number>) {
let str = ""
for (let [key, value] of Object.entries(json)) {
str += `${key}=${value}\n`
}
return str
}

3
packages/cli/start.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
dir="$(dirname -- "$(readlink -f "${BASH_SOURCE}")")"
${dir}/node_modules/ts-node/dist/bin.js ${dir}/src/index.ts $@

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": ["es2020"],
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"types": [ "node", "jest" ],
"outDir": "dist",
"skipLibCheck": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts",
"**/*.spec.js"
]
}

View File

@ -0,0 +1,32 @@
{
"extends": "./tsconfig.build.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@budibase/types": ["../types/src"],
"@budibase/backend-core": ["../backend-core/src"],
"@budibase/backend-core/*": ["../backend-core/*"],
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"]
}
},
"ts-node": {
"require": ["tsconfig-paths/register"],
"swc": true
},
"references": [
{ "path": "../types" },
{ "path": "../backend-core" },
{ "path": "../../../budibase-pro/packages/pro" }
],
"include": [
"src/**/*",
"package.json"
],
"exclude": [
"node_modules",
"dist"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
import { Event } from "../events"
export enum CommandWord {
BACKUPS = "backups",
HOSTING = "hosting",
ANALYTICS = "analytics",
HELP = "help",
PLUGIN = "plugins",
}
export enum InitType {
QUICK = "quick",
DIGITAL_OCEAN = "do",
}
export const AnalyticsEvent = {
OptOut: "analytics:opt:out",
OptIn: "analytics:opt:in",
SelfHostInit: "hosting:init",
PluginInit: Event.PLUGIN_INIT,
}

View File

@ -0,0 +1 @@
export * from "./constants"

View File

@ -15,3 +15,4 @@ export * from "./environmentVariables"
export * from "./auditLogs"
export * from "./sso"
export * from "./user"
export * from "./cli"