@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,60 @@
1
+ import { Command } from "../structures/Command"
2
+ import { CommandWord } from "../constants"
3
+ import { success, error } from "../utils"
4
+ import { AnalyticsClient } from "./Client"
5
+
6
+ const client = new AnalyticsClient()
7
+
8
+ async function optOut() {
9
+ try {
10
+ // opt them out
11
+ client.disable()
12
+ console.log(
13
+ success(
14
+ "Successfully opted out of Budibase analytics. You can opt in at any time by running 'budi analytics opt-in'"
15
+ )
16
+ )
17
+ } catch (err: any) {
18
+ console.log(
19
+ error(
20
+ `Error opting out of Budibase analytics. Please try again later - ${err}`
21
+ )
22
+ )
23
+ }
24
+ }
25
+
26
+ async function optIn() {
27
+ try {
28
+ // opt them in
29
+ client.enable()
30
+ console.log(
31
+ success(
32
+ "Successfully opted in to Budibase analytics. Thank you for helping us make Budibase better!"
33
+ )
34
+ )
35
+ } catch (err) {
36
+ console.log(
37
+ error("Error opting in to Budibase analytics. Please try again later.")
38
+ )
39
+ }
40
+ }
41
+
42
+ async function status() {
43
+ try {
44
+ console.log(success(`Budibase analytics ${client.status()}`))
45
+ } catch (err) {
46
+ console.log(
47
+ error("Error fetching analytics status. Please try again later.")
48
+ )
49
+ }
50
+ }
51
+
52
+ export default new Command(`${CommandWord.ANALYTICS}`)
53
+ .addHelp("Control the analytics you send to Budibase.")
54
+ .addSubOption("--optin", "Opt in to sending analytics to Budibase", optIn)
55
+ .addSubOption("--optout", "Opt out of sending analytics to Budibase.", optOut)
56
+ .addSubOption(
57
+ "--status",
58
+ "Check whether you are currently opted in to Budibase analytics.",
59
+ status
60
+ )
@@ -0,0 +1,130 @@
1
+ import { Command } from "../structures/Command"
2
+ import { CommandWord } from "../constants"
3
+ import fs from "fs"
4
+ import { join } from "path"
5
+ import { getAllDbs } from "../core/db"
6
+ import { progressBar, httpCall } from "../utils"
7
+ import {
8
+ TEMP_DIR,
9
+ COUCH_DIR,
10
+ MINIO_DIR,
11
+ getConfig,
12
+ replication,
13
+ getPouches,
14
+ } from "./utils"
15
+ import { exportObjects, importObjects } from "./objectStore"
16
+ const tar = require("tar")
17
+
18
+ type BackupOpts = { env?: string; import?: string; export?: string }
19
+
20
+ async function exportBackup(opts: BackupOpts) {
21
+ const envFile = opts.env || undefined
22
+ let filename = opts["export"] || (opts as string)
23
+ if (typeof filename !== "string") {
24
+ filename = `backup-${new Date().toISOString()}.tar.gz`
25
+ }
26
+ const config = await getConfig(envFile)
27
+ const dbList = (await getAllDbs(config["COUCH_DB_URL"])) as string[]
28
+ const { Remote, Local } = getPouches(config)
29
+ if (fs.existsSync(TEMP_DIR)) {
30
+ fs.rmSync(TEMP_DIR, { recursive: true })
31
+ }
32
+ const couchDir = join(TEMP_DIR, COUCH_DIR)
33
+ fs.mkdirSync(TEMP_DIR)
34
+ fs.mkdirSync(couchDir)
35
+ console.log("CouchDB Export")
36
+ const bar = progressBar(dbList.length)
37
+ let count = 0
38
+ for (let db of dbList) {
39
+ bar.update(++count)
40
+ const remote = new Remote(db)
41
+ const local = new Local(join(TEMP_DIR, COUCH_DIR, db))
42
+ await replication(remote, local)
43
+ }
44
+ bar.stop()
45
+ console.log("S3 Export")
46
+ await exportObjects()
47
+ tar.create(
48
+ {
49
+ sync: true,
50
+ gzip: true,
51
+ file: filename,
52
+ cwd: join(TEMP_DIR),
53
+ },
54
+ [COUCH_DIR, MINIO_DIR]
55
+ )
56
+ fs.rmSync(TEMP_DIR, { recursive: true })
57
+ console.log(`Generated export file - ${filename}`)
58
+ }
59
+
60
+ async function importBackup(opts: BackupOpts) {
61
+ const envFile = opts.env || undefined
62
+ const filename = opts["import"] || (opts as string)
63
+ const config = await getConfig(envFile)
64
+ if (!filename || !fs.existsSync(filename)) {
65
+ console.error("Cannot import without specifying a valid file to import")
66
+ process.exit(-1)
67
+ }
68
+ if (fs.existsSync(TEMP_DIR)) {
69
+ fs.rmSync(TEMP_DIR, { recursive: true })
70
+ }
71
+ fs.mkdirSync(TEMP_DIR)
72
+ tar.extract({
73
+ sync: true,
74
+ cwd: join(TEMP_DIR),
75
+ file: filename,
76
+ })
77
+ const { Remote, Local } = getPouches(config)
78
+ const dbList = fs.readdirSync(join(TEMP_DIR, COUCH_DIR))
79
+ console.log("CouchDB Import")
80
+ const bar = progressBar(dbList.length)
81
+ let count = 0
82
+ for (let db of dbList) {
83
+ bar.update(++count)
84
+ const remote = new Remote(db)
85
+ const local = new Local(join(TEMP_DIR, COUCH_DIR, db))
86
+ await replication(local, remote)
87
+ }
88
+ bar.stop()
89
+ console.log("MinIO Import")
90
+ await importObjects()
91
+ // finish by letting the system know that a restore has occurred
92
+ try {
93
+ await httpCall(
94
+ `http://localhost:${config.MAIN_PORT}/api/system/restored`,
95
+ "POST"
96
+ )
97
+ } catch (err) {
98
+ // ignore error - it will be an older system
99
+ }
100
+ console.log("Import complete")
101
+ fs.rmSync(TEMP_DIR, { recursive: true })
102
+ }
103
+
104
+ async function pickOne(opts: BackupOpts) {
105
+ if (opts["import"]) {
106
+ return importBackup(opts)
107
+ } else if (opts["export"]) {
108
+ return exportBackup(opts)
109
+ }
110
+ }
111
+
112
+ export default new Command(`${CommandWord.BACKUPS}`)
113
+ .addHelp(
114
+ "Allows building backups of Budibase, as well as importing a backup to a new instance."
115
+ )
116
+ .addSubOption(
117
+ "--export [filename]",
118
+ "Export a backup from an existing Budibase installation.",
119
+ exportBackup
120
+ )
121
+ .addSubOption(
122
+ "--import [filename]",
123
+ "Import a backup to a new Budibase installation.",
124
+ importBackup
125
+ )
126
+ .addSubOption(
127
+ "--env [envFile]",
128
+ "Provide an environment variable file to configure the CLI.",
129
+ pickOne
130
+ )
@@ -0,0 +1,69 @@
1
+ import { objectStore } from "@budibase/backend-core"
2
+ import fs from "fs"
3
+ import { join } from "path"
4
+ import { TEMP_DIR, MINIO_DIR } from "./utils"
5
+ import { progressBar } from "../utils"
6
+ const {
7
+ ObjectStoreBuckets,
8
+ ObjectStore,
9
+ retrieve,
10
+ uploadDirectory,
11
+ makeSureBucketExists,
12
+ } = objectStore
13
+
14
+ const bucketList = Object.values(ObjectStoreBuckets)
15
+
16
+ export async function exportObjects() {
17
+ const path = join(TEMP_DIR, MINIO_DIR)
18
+ fs.mkdirSync(path)
19
+ let fullList: any[] = []
20
+ let errorCount = 0
21
+ for (let bucket of bucketList) {
22
+ const client = ObjectStore(bucket)
23
+ try {
24
+ await client.headBucket().promise()
25
+ } catch (err) {
26
+ errorCount++
27
+ continue
28
+ }
29
+ const list = (await client.listObjectsV2().promise()) as { Contents: any[] }
30
+ fullList = fullList.concat(list.Contents.map(el => ({ ...el, bucket })))
31
+ }
32
+ if (errorCount === bucketList.length) {
33
+ throw new Error("Unable to access MinIO/S3 - check environment config.")
34
+ }
35
+ const bar = progressBar(fullList.length)
36
+ let count = 0
37
+ for (let object of fullList) {
38
+ const filename = object.Key
39
+ const data = await retrieve(object.bucket, filename)
40
+ const possiblePath = filename.split("/")
41
+ if (possiblePath.length > 1) {
42
+ const dirs = possiblePath.slice(0, possiblePath.length - 1)
43
+ fs.mkdirSync(join(path, object.bucket, ...dirs), { recursive: true })
44
+ }
45
+ fs.writeFileSync(join(path, object.bucket, ...possiblePath), data)
46
+ bar.update(++count)
47
+ }
48
+ bar.stop()
49
+ }
50
+
51
+ export async function importObjects() {
52
+ const path = join(TEMP_DIR, MINIO_DIR)
53
+ const buckets = fs.readdirSync(path)
54
+ let total = 0
55
+ buckets.forEach(bucket => {
56
+ const files = fs.readdirSync(join(path, bucket))
57
+ total += files.length
58
+ })
59
+ const bar = progressBar(total)
60
+ let count = 0
61
+ for (let bucket of buckets) {
62
+ const client = ObjectStore(bucket)
63
+ await makeSureBucketExists(client, bucket)
64
+ const files = await uploadDirectory(bucket, join(path, bucket), "/")
65
+ count += files.length
66
+ bar.update(count)
67
+ }
68
+ bar.stop()
69
+ }
@@ -0,0 +1,99 @@
1
+ import dotenv from "dotenv"
2
+ import fs from "fs"
3
+ import { string } from "../questions"
4
+ import { getPouch } from "../core/db"
5
+ import { env as environment } from "@budibase/backend-core"
6
+ import PouchDB from "pouchdb"
7
+
8
+ export const TEMP_DIR = ".temp"
9
+ export const COUCH_DIR = "couchdb"
10
+ export const MINIO_DIR = "minio"
11
+
12
+ const REQUIRED = [
13
+ { value: "MAIN_PORT", default: "10000" },
14
+ {
15
+ value: "COUCH_DB_URL",
16
+ default: "http://budibase:budibase@localhost:10000/db/",
17
+ },
18
+ { value: "MINIO_URL", default: "http://localhost:10000" },
19
+ { value: "MINIO_ACCESS_KEY" },
20
+ { value: "MINIO_SECRET_KEY" },
21
+ ]
22
+
23
+ export function checkURLs(config: Record<string, string>) {
24
+ const mainPort = config["MAIN_PORT"],
25
+ username = config["COUCH_DB_USER"],
26
+ password = config["COUCH_DB_PASSWORD"]
27
+ if (!config["COUCH_DB_URL"] && mainPort && username && password) {
28
+ config[
29
+ "COUCH_DB_URL"
30
+ ] = `http://${username}:${password}@localhost:${mainPort}/db/`
31
+ }
32
+ if (!config["MINIO_URL"]) {
33
+ config["MINIO_URL"] = `http://localhost:${mainPort}/`
34
+ }
35
+ return config
36
+ }
37
+
38
+ export async function askQuestions() {
39
+ console.log(
40
+ "*** NOTE: use a .env file to load these parameters repeatedly ***"
41
+ )
42
+ let config: Record<string, string> = {}
43
+ for (let property of REQUIRED) {
44
+ config[property.value] = await string(property.value, property.default)
45
+ }
46
+ return config
47
+ }
48
+
49
+ export function loadEnvironment(path: string) {
50
+ if (!fs.existsSync(path)) {
51
+ throw "Unable to file specified .env file"
52
+ }
53
+ const env = fs.readFileSync(path, "utf8")
54
+ const config = checkURLs(dotenv.parse(env))
55
+ for (let required of REQUIRED) {
56
+ if (!config[required.value]) {
57
+ throw `Cannot find "${required.value}" property in .env file`
58
+ }
59
+ }
60
+ return config
61
+ }
62
+
63
+ // true is the default value passed by commander
64
+ export async function getConfig(envFile: boolean | string = true) {
65
+ let config
66
+ if (envFile !== true) {
67
+ config = loadEnvironment(envFile as string)
68
+ } else {
69
+ config = await askQuestions()
70
+ }
71
+ // fill out environment
72
+ for (let key of Object.keys(config)) {
73
+ environment._set(key, config[key])
74
+ }
75
+ return config
76
+ }
77
+
78
+ export async function replication(
79
+ from: PouchDB.Database,
80
+ to: PouchDB.Database
81
+ ) {
82
+ const pouch = getPouch()
83
+ try {
84
+ await pouch.replicate(from, to, {
85
+ batch_size: 1000,
86
+ batches_limit: 5,
87
+ // @ts-ignore
88
+ style: "main_only",
89
+ })
90
+ } catch (err) {
91
+ throw new Error(`Replication failed - ${JSON.stringify(err)}`)
92
+ }
93
+ }
94
+
95
+ export function getPouches(config: Record<string, string>) {
96
+ const Remote = getPouch(config["COUCH_DB_URL"])
97
+ const Local = getPouch()
98
+ return { Remote, Local }
99
+ }
@@ -0,0 +1,4 @@
1
+ export { CommandWord, InitType, AnalyticsEvent } from "@budibase/types"
2
+
3
+ export const POSTHOG_TOKEN = "phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS"
4
+ export const GENERATED_USER_EMAIL = "admin@admin.com"
package/src/core/db.ts ADDED
@@ -0,0 +1,39 @@
1
+ import PouchDB from "pouchdb"
2
+ import { checkSlashesInUrl } from "../utils"
3
+ import fetch from "node-fetch"
4
+
5
+ /**
6
+ * Fully qualified URL including username and password, or nothing for local
7
+ */
8
+ export function getPouch(url?: string) {
9
+ let POUCH_DB_DEFAULTS
10
+ if (!url) {
11
+ POUCH_DB_DEFAULTS = {
12
+ prefix: undefined,
13
+ adapter: "leveldb",
14
+ }
15
+ } else {
16
+ POUCH_DB_DEFAULTS = {
17
+ prefix: url,
18
+ }
19
+ }
20
+ const replicationStream = require("pouchdb-replication-stream")
21
+ PouchDB.plugin(replicationStream.plugin)
22
+ // @ts-ignore
23
+ PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
24
+ return PouchDB.defaults(POUCH_DB_DEFAULTS) as PouchDB.Static
25
+ }
26
+
27
+ export async function getAllDbs(url: string) {
28
+ const response = await fetch(
29
+ checkSlashesInUrl(encodeURI(`${url}/_all_dbs`)),
30
+ {
31
+ method: "GET",
32
+ }
33
+ )
34
+ if (response.status === 200) {
35
+ return await response.json()
36
+ } else {
37
+ throw "Cannot connect to CouchDB instance"
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ process.env.NO_JS = "1"
2
+ process.env.JS_BCRYPT = "1"
3
+ process.env.DISABLE_JWT_WARNING = "1"
package/src/events.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { AnalyticsClient } from "./analytics/Client"
2
+
3
+ const client = new AnalyticsClient()
4
+
5
+ export function captureEvent(event: string, properties: any) {
6
+ client.capture({
7
+ distinctId: "cli",
8
+ event,
9
+ properties,
10
+ })
11
+ }
package/src/exec.ts ADDED
@@ -0,0 +1,27 @@
1
+ import util from "util"
2
+ const runCommand = util.promisify(require("child_process").exec)
3
+
4
+ export async function exec(command: string, dir = "./") {
5
+ const { stdout } = await runCommand(command, { cwd: dir })
6
+ return stdout
7
+ }
8
+
9
+ export async function utilityInstalled(utilName: string) {
10
+ try {
11
+ await exec(`${utilName} --version`)
12
+ return true
13
+ } catch (err) {
14
+ return false
15
+ }
16
+ }
17
+
18
+ export async function runPkgCommand(command: string, dir = "./") {
19
+ const yarn = await exports.utilityInstalled("yarn")
20
+ const npm = await exports.utilityInstalled("npm")
21
+ if (!yarn && !npm) {
22
+ throw new Error("Must have yarn or npm installed to run build.")
23
+ }
24
+ const npmCmd = command === "install" ? `npm ${command}` : `npm run ${command}`
25
+ const cmd = yarn ? `yarn ${command} --ignore-engines` : npmCmd
26
+ await exports.exec(cmd, dir)
27
+ }
@@ -0,0 +1,23 @@
1
+ const { success } = require("../utils")
2
+ const { updateDockerComposeService } = require("./utils")
3
+ const randomString = require("randomstring")
4
+ const { GENERATED_USER_EMAIL } = require("../constants")
5
+ import { DockerCompose } from "./types"
6
+
7
+ export async function generateUser(password: string | null, silent: boolean) {
8
+ const email = GENERATED_USER_EMAIL
9
+ if (!password) {
10
+ password = randomString.generate({ length: 6 })
11
+ }
12
+ updateDockerComposeService((service: DockerCompose) => {
13
+ service.environment["BB_ADMIN_USER_EMAIL"] = email
14
+ service.environment["BB_ADMIN_USER_PASSWORD"] = password as string
15
+ })
16
+ if (!silent) {
17
+ console.log(
18
+ success(
19
+ `User admin credentials configured, access with email: ${email} - password: ${password}`
20
+ )
21
+ )
22
+ }
23
+ }
@@ -0,0 +1,48 @@
1
+ import { Command } from "../structures/Command"
2
+ import { CommandWord } from "../constants"
3
+ import { init } from "./init"
4
+ import { start } from "./start"
5
+ import { stop } from "./stop"
6
+ import { status } from "./status"
7
+ import { update } from "./update"
8
+ import { generateUser } from "./genUser"
9
+ import { watchPlugins } from "./watch"
10
+
11
+ export default new Command(`${CommandWord.HOSTING}`)
12
+ .addHelp("Controls self hosting on the Budibase platform.")
13
+ .addSubOption(
14
+ "--init [type]",
15
+ "Configure a self hosted platform in current directory, type can be unspecified, 'quick' or 'single'.",
16
+ init
17
+ )
18
+ .addSubOption(
19
+ "--start",
20
+ "Start the configured platform in current directory.",
21
+ start
22
+ )
23
+ .addSubOption(
24
+ "--status",
25
+ "Check the status of currently running services.",
26
+ status
27
+ )
28
+ .addSubOption(
29
+ "--stop",
30
+ "Stop the configured platform in the current directory.",
31
+ stop
32
+ )
33
+ .addSubOption(
34
+ "--update",
35
+ "Update the Budibase images to the latest version.",
36
+ update
37
+ )
38
+ .addSubOption(
39
+ "--watch-plugin-dir [directory]",
40
+ "Add plugin directory watching to a Budibase install.",
41
+ watchPlugins
42
+ )
43
+ .addSubOption(
44
+ "--gen-user",
45
+ "Create an admin user automatically as part of first start.",
46
+ generateUser
47
+ )
48
+ .addSubOption("--single", "Specify this with init to use the single image.")
@@ -0,0 +1,76 @@
1
+ import { InitType, AnalyticsEvent } from "../constants"
2
+ import { confirmation } from "../questions"
3
+ import { captureEvent } from "../events"
4
+ import * as makeFiles from "./makeFiles"
5
+ import { parseEnv } from "../utils"
6
+ import { checkDockerConfigured, downloadDockerCompose } from "./utils"
7
+ import { watchPlugins } from "./watch"
8
+ import { generateUser } from "./genUser"
9
+ import fetch from "node-fetch"
10
+
11
+ const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
12
+
13
+ async function getInitConfig(type: string, isQuick: boolean, port: number) {
14
+ const config: any = isQuick ? makeFiles.QUICK_CONFIG : {}
15
+ if (type === InitType.DIGITAL_OCEAN) {
16
+ try {
17
+ const output = await fetch(DO_USER_DATA_URL)
18
+ const data = await output.text()
19
+ const response = parseEnv(data)
20
+ for (let [key, value] of Object.entries(makeFiles.ConfigMap)) {
21
+ if (response[key]) {
22
+ config[value as string] = response[key]
23
+ }
24
+ }
25
+ } catch (err) {
26
+ // don't need to handle error, just don't do anything
27
+ }
28
+ }
29
+ // override port
30
+ if (port) {
31
+ config[makeFiles.ConfigMap.MAIN_PORT] = port
32
+ }
33
+ return config
34
+ }
35
+
36
+ export async function init(opts: any) {
37
+ let type, isSingle, watchDir, genUser, port, silent
38
+ if (typeof opts === "string") {
39
+ type = opts
40
+ } else {
41
+ type = opts["init"]
42
+ isSingle = opts["single"]
43
+ watchDir = opts["watchPluginDir"]
44
+ genUser = opts["genUser"]
45
+ port = opts["port"]
46
+ silent = opts["silent"]
47
+ }
48
+ const isQuick = type === InitType.QUICK || type === InitType.DIGITAL_OCEAN
49
+ await checkDockerConfigured()
50
+ if (!isQuick) {
51
+ const shouldContinue = await confirmation(
52
+ "This will create multiple files in current directory, should continue?"
53
+ )
54
+ if (!shouldContinue) {
55
+ console.log("Stopping.")
56
+ return
57
+ }
58
+ }
59
+ captureEvent(AnalyticsEvent.SelfHostInit, {
60
+ type,
61
+ })
62
+ const config = await getInitConfig(type, isQuick, port)
63
+ if (!isSingle) {
64
+ await downloadDockerCompose()
65
+ await makeFiles.makeEnv(config, silent)
66
+ } else {
67
+ await makeFiles.makeSingleCompose(config, silent)
68
+ }
69
+ if (watchDir) {
70
+ await watchPlugins(watchDir, silent)
71
+ }
72
+ if (genUser) {
73
+ const inputPassword = typeof genUser === "string" ? genUser : null
74
+ await generateUser(inputPassword, silent)
75
+ }
76
+ }