proca
Version:
254 lines (230 loc) • 6.34 kB
JavaScript
import { Args, Command, Flags, ux } from "@oclif/core";
import debug from "debug";
import { parse as dxid, id } from "dxid";
import Table from "easy-table";
import fastcsv from "fast-csv";
import initHook from "#src/hooks/init.mjs";
import { createClient } from "#src/urql.mjs";
class ProcaCommand extends Command {
static enableJsonFlag = true;
procaConfig = { url: "https://api.proca.app/api" };
format = "human"; // the default formatting
flags = {};
static baseFlags = {
env: Flags.string({
default: "default",
description: "allow to switch between configurations (server or users)",
}),
human: Flags.boolean({
helpGroup: "OUTPUT", // Optional, groups it under a specific help section if desired
description: "Format output to be read on screen by a human [default]",
default: true,
}),
json: Flags.boolean({
helpGroup: "OUTPUT", // Optional, groups it under a specific help section if desired
description: "Format output as json",
exclusive: ["human", "csv"],
}),
csv: Flags.boolean({
description: "Format output as csv",
helpGroup: "OUTPUT", // Optional, groups it under a specific help section if desired
}),
simplify: Flags.boolean({
helpGroup: "OUTPUT",
description:
"flatten and filter to output only the most important attributes, mostly relevant for json",
allowNo: true,
}),
};
static multiid() {
const args = {
id_name_dxid: Args.string({
ignoreStdin: true,
hidden: true,
description:
"it's better to use -i <id> or -x <dxid> or -n <name>, but you can for convenience",
}),
};
return args;
}
static flagify(params = {}) {
const flags = Object.assign({}, ProcaCommand.baseFlags);
if (params.multiid) {
flags.id = Flags.string({
char: "i",
parse: (input) => Number.parseInt(input, 10),
exclusive: ["name", "dxid"],
});
flags.dxid = Flags.string({
char: "x",
description: "dxid",
});
flags.name = Flags.string({
char: "n",
charAliases: ["o"],
description: "name",
helpValue: "<the_short_name>",
});
}
return flags;
}
async parse() {
const parsed = await super.parse();
const maybe = parsed.args.id_name_dxid;
if (maybe) {
const d = dxid(maybe, false);
if (d) parsed.flags.id = d;
else parsed.flags.name = maybe;
}
if (parsed.flags.dxid) {
parsed.flags.id = dxid(parsed.flags.dxid);
}
return parsed;
}
static hooks = {
init: async (options) => {
console.log("init hook called", options);
process.exit(1);
},
};
async init() {
await super.init();
const { argv, flags } = await this.parse();
this.flags = flags;
if (flags.json) this.format = "json";
if (flags.csv) this.format = "csv";
this.debug = debug("proca");
initHook({ config: this.config });
this.procaConfig = this.config.procaConfig; // set up from the hooks/init
// await this.config.runHook('init', { config: this.config });
createClient(this.procaConfig);
}
async _catch(err) {
// Check if the error was caused by a missing flag or wrong argument format
console.log("aaa", err);
try {
this.error(err.toString());
} catch (e) {
console.log(e);
}
}
flatten = (obj, prefix = "", result = {}) => {
Object.entries(obj).forEach(([k, v]) => {
const newKey = Object.hasOwn(result, k) && prefix ? `${prefix}-${k}` : k;
if (v?.constructor === Object) {
this.flatten(v, newKey, result);
} else {
result[newKey] = v;
}
});
return result;
};
simplify = (d) => {
const r = {};
for (const [key, value] of Object.entries(d)) {
if (key === "__typename") continue;
if (key === "config" && typeof value === "string") continue; // it's just a giant mess if not processed, let's skipt
if (value === null) continue;
if (typeof value === "string" || typeof value === "number") {
r[key] = value;
continue;
}
if (typeof value === "object") {
if (value?.name) r[key] = value.name;
continue;
}
r[key] = value;
}
return r;
};
tlog(color, ...msg) {
const coloredMsg = msg.map((d) => ux.colorize(this.config.theme[color], d));
this.log(...coloredMsg);
}
info(...msg) {
this.tlog("info", msg);
}
prettyJson(obj) {
if (typeof obj === "string") {
obj = JSON.parse(obj);
}
this.log(ux.colorizeJson(obj, { theme: this.config.theme.json }));
}
warn(...msg) {
this.tlog("warn", ...msg);
}
error(msg, options = {}) {
const colouredMessage = ux.colorize(this.config.theme.error, msg);
super.error(colouredMessage, options);
}
async csv(data) {
return new Promise((resolve, reject) => {
let d = null;
const format = this.flags.simplify
? this.simplify
: (d) => this.flatten(d, "");
if (Array.isArray(data)) {
d = data.map(format);
} else {
d = [format(data)];
}
const stream = fastcsv
.write(d, { headers: true })
.on("finish", () => {
console.log();
resolve();
})
.on("error", reject);
stream.pipe(process.stdout);
});
}
table(data, transformRow, print = (table) => table.toString()) {
if (!transformRow) {
if (this.flags.simplify !== false) {
transformRow = (d, cell, idx) => {
const r = this.simplify(d);
if (r === null) return null;
for (const [key, value] of Object.entries(r)) {
cell(key, value);
}
return true;
};
} else {
transformRow = (d, cell, idx) => {
for (const [key, value] of Object.entries(this.flatten(d))) {
cell(key, value);
}
return true;
};
}
}
const theme = this.config.theme;
Table.prototype.pushDelimeter = function (cols) {
// hack to change the formatting of the header
cols = cols || this.columns();
cols.forEach(function (col) {
this.cell(
col,
undefined,
Table.leftPadder(ux.colorize(theme.flagSeparator, "-")),
);
}, this);
return this.newRow();
};
this.log(Table.print(data, transformRow, print));
}
async output(data) {
if (this.format === "json") {
if (this.flags.simplify)
return data?.map(this.simplify) || this.simplify(data);
const isDirectCall = process.argv.join(":").includes(this.id);
return data;
}
if (this.format === "csv") {
return this.csv(data);
}
return this.table(data);
}
}
export { ProcaCommand as Command, Args, Flags };
export default ProcaCommand;