Basic plugin installation solid
This commit is contained in:
commit
1cba10695e
16
manifest.yml
Normal file
16
manifest.yml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
_version: '20220212062110'
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
echo:
|
||||||
|
remote: '/home/daniel/code/pluggable-cli/plugins/echo'
|
||||||
|
run: '{plugin_dir}/echo.sh'
|
||||||
|
# preInstallCommand:
|
||||||
|
# installCommand: git clone $remote
|
||||||
|
# may also want to checkout a provided version tag and copy contents
|
||||||
|
# and only clone to a "plugin-repos" dir
|
||||||
|
# might want to omit git history, too?
|
||||||
|
# postInstallCommand:
|
||||||
|
|
||||||
|
shutup:
|
||||||
|
remote: '/home/daniel/code/pluggable-cli/plugins/shutup'
|
||||||
|
run: '{plugin_dir}/shutup.sh'
|
41
readme.md
Normal file
41
readme.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Pluggable CLI
|
||||||
|
|
||||||
|
- Each subcommand is a plugin?
|
||||||
|
- Versioning and/or stability will be important?
|
||||||
|
- Maybe plugins _never_ change and instead you prefix them with a version?
|
||||||
|
- How can we build stability into the system?
|
||||||
|
- Some subset or all plugins are already known
|
||||||
|
- Auto-download plugins when attempting to run a command
|
||||||
|
- Completions for subcommands
|
||||||
|
- Are completions provided for commands not-yet-installed? Does attempting to
|
||||||
|
complete a subcommand's commands install the plugin and process its
|
||||||
|
completions?
|
||||||
|
- Will have configuration
|
||||||
|
|
||||||
|
# Core functions
|
||||||
|
|
||||||
|
- What plugins are available to me?
|
||||||
|
- HTTP GET (and cache?) some known human- and machine-readable manifest
|
||||||
|
- JSON, YAML, Cue, or Ion?
|
||||||
|
- Install a plugin
|
||||||
|
- HTTP GET
|
||||||
|
- Delete a plugin
|
||||||
|
- rm -r dir
|
||||||
|
- Update self
|
||||||
|
- download new binary and replace self with it
|
||||||
|
- Update plugin
|
||||||
|
- replace plugin
|
||||||
|
- Run a plugin with some given arguments
|
||||||
|
- call plugin with args
|
||||||
|
|
||||||
|
# Components I See
|
||||||
|
|
||||||
|
- Core
|
||||||
|
- Knows where to find manifest (may cache locally)
|
||||||
|
- Installs, updates, deletes plugins using information in manifest
|
||||||
|
- Can update or uninstall itself
|
||||||
|
- Can run plugins
|
||||||
|
- Manifest
|
||||||
|
- Contains information about where to find plugins and their versions
|
||||||
|
- Plugins
|
||||||
|
- Probably dumb scripts that call fancier things
|
95
src/core.ts
Normal file
95
src/core.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import {yaml, fs, path} from "./deps.ts"
|
||||||
|
|
||||||
|
const MANIFEST_URL = "file:///home/daniel/code/pluggable-cli/manifest.yml"
|
||||||
|
const PLUGINS_DIR = "/home/daniel/.home/.cache/installed-plugins"
|
||||||
|
|
||||||
|
fs.ensureDir(PLUGINS_DIR)
|
||||||
|
|
||||||
|
const verboseOutput = true
|
||||||
|
|
||||||
|
const responseBody = await (await fetch(MANIFEST_URL)).text()
|
||||||
|
const manifest: any = yaml.parse(responseBody)
|
||||||
|
|
||||||
|
for await (const dir of Deno.readDir(PLUGINS_DIR)) {
|
||||||
|
if (dir.isDirectory) {
|
||||||
|
if (manifest.plugins[dir.name]) {
|
||||||
|
manifest.plugins[dir.name].installed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.debug(manifest)
|
||||||
|
|
||||||
|
async function usage() {
|
||||||
|
console.info("cli-poc: a CLI proof-of-concept")
|
||||||
|
console.info(" -h, --help: Show usage")
|
||||||
|
console.info(" install-plugin <plugin-name>")
|
||||||
|
console.info(" update-plugin <plugin-name>")
|
||||||
|
console.info(" remove-plugin <plugin-name>")
|
||||||
|
console.info(" list-plugins")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listPlugins() {
|
||||||
|
for (const pluginName in manifest.plugins) {
|
||||||
|
let text = pluginName
|
||||||
|
if (manifest.plugins[pluginName].installed) text += " [installed]"
|
||||||
|
console.info(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installedPlugins() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installPlugin(pluginName: string) {
|
||||||
|
const pluginManifestData = manifest.plugins[pluginName]
|
||||||
|
if (!pluginManifestData) {
|
||||||
|
console.error(`plugin ${pluginName} has no entry`)
|
||||||
|
Deno.exit(1)
|
||||||
|
}
|
||||||
|
const cmd = ["git", "clone", pluginManifestData.remote, pluginName]
|
||||||
|
const installCommand = Deno.run({
|
||||||
|
cwd: PLUGINS_DIR,
|
||||||
|
stdout: "piped",
|
||||||
|
stderr: "piped",
|
||||||
|
cmd
|
||||||
|
})
|
||||||
|
const status = await installCommand.status()
|
||||||
|
if (status.code != 0) {
|
||||||
|
console.error(`Installing plugin using command ${cmd} failed:\n${await installCommand.output()}\n${await installCommand.stderrOutput()}`)
|
||||||
|
Deno.exit(status.code)
|
||||||
|
}
|
||||||
|
return installCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePlugin(pluginName: string) {
|
||||||
|
await fs.emptyDir(path.join(PLUGINS_DIR, pluginName))
|
||||||
|
await Deno.remove(path.join(PLUGINS_DIR, pluginName))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updatePlugin(pluginName: string) {
|
||||||
|
await deletePlugin(pluginName)
|
||||||
|
await installPlugin(pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcommand = Deno.args[0]
|
||||||
|
if (Deno.args.includes("-h") || subcommand == "help" || subcommand == "" || Deno.args.includes("--help")) {
|
||||||
|
usage()
|
||||||
|
} else if (subcommand == "update-plugin") {
|
||||||
|
await updatePlugin(Deno.args[1])
|
||||||
|
} else if (subcommand == "remove-plugin") {
|
||||||
|
await deletePlugin(Deno.args[1])
|
||||||
|
} else if (subcommand == "install-plugin") {
|
||||||
|
await installPlugin(Deno.args[1])
|
||||||
|
} else if (subcommand == "list-plugins") {
|
||||||
|
listPlugins()
|
||||||
|
} else {
|
||||||
|
if (manifest.plugins[subcommand].installed !== true) {
|
||||||
|
// console.warn(`Installing missing ${subcommand} plugin...`)
|
||||||
|
await installPlugin(subcommand)
|
||||||
|
}
|
||||||
|
const subcommandCommand = Deno.run({
|
||||||
|
cmd: [manifest.plugins[subcommand].run.replace("{plugin_dir}", path.join(PLUGINS_DIR, subcommand))].concat(Deno.args.slice(1))
|
||||||
|
})
|
||||||
|
const status = await subcommandCommand.status()
|
||||||
|
if (status.code != 0) Deno.exit(status.code)
|
||||||
|
}
|
6
src/deps.ts
Normal file
6
src/deps.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export * as xdg from 'https://deno.land/x/xdg@v9.4.0/src/mod.deno.ts';
|
||||||
|
export * as media_types from "https://deno.land/x/media_types@v2.12.1/mod.ts";
|
||||||
|
export * as compare_versions from "https://deno.land/x/compare_versions@0.4.0/mod.ts";
|
||||||
|
export * as yaml from "https://deno.land/std@0.125.0/encoding/yaml.ts"
|
||||||
|
export * as fs from "https://deno.land/std@0.125.0/fs/mod.ts";
|
||||||
|
export * as path from "https://deno.land/std@0.125.0/path/mod.ts";
|
Loading…
Reference in a new issue