1
0
Fork 0
mirror of synced 2024-09-24 21:31:17 +12:00
budibase/packages/cli/src/plugins/index.ts
2023-11-20 21:52:29 +01:00

203 lines
5.9 KiB
TypeScript

import { Command } from "../structures/Command"
import {
CommandWord,
AnalyticsEvent,
InitType,
GENERATED_USER_EMAIL,
} 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 { init as hostingInit } from "../hosting/init"
import { start as hostingStart } from "../hosting/start"
const fp = require("find-free-port")
type PluginOpts = {
init?: PluginType
}
function checkInPlugin() {
if (!fs.existsSync("package.json")) {
throw new Error(
"Please run in a plugin directory - must contain package.json"
)
}
if (!fs.existsSync("schema.json")) {
throw new Error(
"Please run in a plugin directory - must contain schema.json"
)
}
}
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")) {
return false
} else {
console.log(
info(`By default the plugin will be created in the directory "${name}"`)
)
console.log(
info(
"if you are already in an empty directory, such as a new Git repo, you can disable this functionality."
)
)
return questions.confirmation("Create top level directory?")
}
}
async function init(opts: PluginOpts | PluginType) {
const type = (opts as PluginOpts).init || (opts as PluginType)
if (!type || !PLUGIN_TYPE_ARR.includes(type)) {
console.log(
error(
"Please provide a type to init, either 'component', 'datasource' or 'automation'."
)
)
return
}
console.log(info("Lets get some details about your new plugin:"))
const name = await questions.string("Name", `budibase-${type}`)
if (fs.existsSync(name)) {
console.log(
error("Directory by plugin name already exists, pick a new name.")
)
return
}
const description = await questions.string(
"Description",
`An amazing Budibase ${type}!`
)
const version = await questions.string("Version", "1.0.0")
const topLevel = await askAboutTopLevel(name)
// get the skeleton
console.log(info("Retrieving project..."))
await getSkeleton(type, name)
await fleshOutSkeleton(type, name, description, version)
console.log(info("Installing dependencies..."))
await runPkgCommand("install", join(process.cwd(), name))
// if no parent directory desired move to cwd
if (!topLevel) {
moveDirectory(name, process.cwd())
console.log(info(`Plugin created in current directory.`))
} else {
console.log(info(`Plugin created in directory "${name}"`))
}
captureEvent(AnalyticsEvent.PluginInit, {
type,
name,
description,
version,
})
}
async function verify() {
// will throw errors if not acceptable
checkInPlugin()
console.log(info("Verifying plugin..."))
const schema = fs.readFileSync("schema.json", "utf8")
const pkg = fs.readFileSync("package.json", "utf8")
let name, version
try {
const schemaJson = JSON.parse(schema)
const pkgJson = JSON.parse(pkg)
if (!pkgJson.name || !pkgJson.version || !pkgJson.description) {
throw new Error(
"package.json is missing one of 'name', 'version' or 'description'."
)
}
name = pkgJson.name
version = pkgJson.version
plugins.validate(schemaJson)
return { name, version }
} 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 {
console.log(error(`Invalid schema/package.json: ${err.message}`))
}
}
}
async function build() {
const verified = await verify()
if (!verified?.name) {
return
}
console.log(success("Verified!"))
console.log(info("Building plugin..."))
await runPkgCommand("build")
const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
console.log(success(`Build complete - output in: ${output}`))
}
async function watch() {
const verified = await verify()
if (!verified?.name) {
return
}
const output = join("dist", `${verified.name}-${verified.version}.tar.gz`)
console.log(info(`Watching - build in: ${output}`))
try {
await runPkgCommand("watch")
} catch (err) {
// always errors when user escapes
console.log(success("Watch exited."))
}
}
async function dev() {
const pluginDir = await questions.string("Directory to watch", "./")
const [port] = await fp(10000)
const password = "admin"
await hostingInit({
init: InitType.QUICK,
single: true,
watchPluginDir: pluginDir,
genUser: password,
port,
silent: true,
})
await hostingStart()
console.log(success(`Configuration has been written to docker-compose.yaml`))
console.log(
success("Development environment started successfully - connect at: ") +
info(`http://localhost:${port}`)
)
console.log(success("Use the following credentials to login:"))
console.log(success("Email: ") + info(GENERATED_USER_EMAIL))
console.log(success("Password: ") + info(password))
}
export default new Command(`${CommandWord.PLUGIN}`)
.addHelp(
"Custom plugins for Budibase, init, build and verify your components and datasources with this tool."
)
.addSubOption(
"--init [type]",
"Init a new plugin project, with a type of either component or datasource.",
init
)
.addSubOption(
"--build",
"Build your plugin, this will verify and produce a final tarball for your project.",
build
)
.addSubOption(
"--watch",
"Automatically build any changes to your plugin.",
watch
)
.addSubOption(
"--dev",
"Run a development environment which automatically watches the current directory.",
dev
)