@wooksjs/event-cli
Version:
@wooksjs/event-cli
380 lines (374 loc) • 12.4 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const __prostojs_cli_help = __toESM(require("@prostojs/cli-help"));
const minimist = __toESM(require("minimist"));
const wooks = __toESM(require("wooks"));
const __wooksjs_event_core = __toESM(require("@wooksjs/event-core"));
//#region packages/event-cli/src/event-cli.ts
function createCliContext(data, options) {
return (0, __wooksjs_event_core.createAsyncEventContext)({
event: {
...data,
type: "CLI"
},
options
});
}
/**
* Wrapper on top of useEventContext that provides
* proper context types for CLI event
* @returns set of hooks { getCtx, restoreCtx, clearCtx, hookStore, getStore, setStore }
*/
function useCliContext() {
return (0, __wooksjs_event_core.useAsyncEventContext)("CLI");
}
//#endregion
//#region packages/event-cli/src/cli-adapter.ts
const cliShortcuts = { cli: "CLI" };
var WooksCli = class extends wooks.WooksAdapterBase {
logger;
cliHelp;
constructor(opts, wooks$1) {
super(wooks$1, opts?.logger, opts?.router);
this.opts = opts;
this.logger = opts?.logger || this.getLogger(`[96m[wooks-cli]`);
this.cliHelp = opts?.cliHelp instanceof __prostojs_cli_help.CliHelpRenderer ? opts.cliHelp : new __prostojs_cli_help.CliHelpRenderer(opts?.cliHelp);
}
/**
* ### Register CLI Command
* Command path segments may be separated by / or space.
*
* For example the folowing path are interpreted the same:
* - "command test use:dev :name"
* - "command/test/use:dev/:name"
*
* Where name will become an argument
*
* ```js
* // example without options
* app.cli('command/:arg', () => 'arg = ' + useRouteParams().params.arg )
*
* // example with options
* app.cli('command/:arg', {
* description: 'Description of the command',
* options: [{ keys: ['project', 'p'], description: 'Description of the option', value: 'myProject' }],
* args: { arg: 'Description of the arg' },
* aliases: ['cmd'], // alias "cmd/:arg" will be registered
* examples: [{
* description: 'Example of usage with someProject',
* cmd: 'argValue -p=someProject',
* // will result in help display:
* // "# Example of usage with someProject\n" +
* // "$ myCli command argValue -p=someProject\n"
* }],
* handler: () => 'arg = ' + useRouteParams().params.arg
* })
* ```
*
* @param path command path
* @param _options handler or options
*
* @returns
*/
cli(path, _options) {
const options = typeof _options === "function" ? { handler: _options } : _options;
const handler = typeof _options === "function" ? _options : _options.handler;
const makePath = (s) => `/${s.replace(/\s+/gu, "/")}`;
const targetPath = makePath(path);
const routed = this.on("CLI", targetPath, handler);
if (options.onRegister) options.onRegister(targetPath, 0, routed);
for (const alias of options.aliases || []) {
const vars = routed.getArgs().map((k) => `:${k}`).join("/");
const targetPath$1 = makePath(alias) + (vars ? `/${vars}` : "");
this.on("CLI", targetPath$1, handler);
if (options.onRegister) options.onRegister(targetPath$1, 1, routed);
}
const command = routed.getStaticPart().replace(/\//gu, " ").trim();
const args = { ...options.args };
for (const arg of routed.getArgs()) if (!args[arg]) args[arg] = "";
this.cliHelp.addEntry({
command,
aliases: options.aliases?.map((alias) => alias.replace(/\\:/gu, ":")),
args,
description: options.description,
examples: options.examples,
options: options.options,
custom: {
handler: options.handler,
cb: options.onRegister
}
});
return routed;
}
alreadyComputedAliases = false;
computeAliases() {
if (!this.alreadyComputedAliases) {
this.alreadyComputedAliases = true;
const aliases = this.cliHelp.getComputedAliases();
for (const [alias, entry] of Object.entries(aliases)) if (entry.custom) {
const vars = Object.keys(entry.args || {}).map((k) => `:${k}`).join("/");
const path = `/${alias.replace(/\s+/gu, "/").replace(/:/gu, "\\:")}${vars ? `/${vars}` : ""}`;
this.on("CLI", path, entry.custom.handler);
if (entry.custom.cb) entry.custom.cb(path, 3);
}
}
}
/**
* ## run
* ### Start command processing
* Triggers command processing
*
* By default takes `process.argv.slice(2)` as a command
*
* It's possible to replace the command by passing an argument
*
* @param _argv optionally overwrite `process.argv.slice(2)` with your `argv` array
*/
async run(_argv, _opts) {
const argv = _argv || process.argv.slice(2);
const parsedFlags = (0, minimist.default)(argv, _opts);
const pathParams = parsedFlags._;
const path = `/${pathParams.map((v) => encodeURI(v).replace(/\//gu, "%2F")).join("/")}`;
const runInContext = createCliContext({
opts: _opts,
argv,
pathParams,
cliHelp: this.cliHelp,
command: path.replace(/\//gu, " ").trim()
}, this.mergeEventOptions(this.opts?.eventOptions));
return runInContext(async () => {
const { store } = useCliContext();
store("flags").value = parsedFlags;
this.computeAliases();
const { handlers: foundHandlers, firstStatic } = this.wooks.lookup("CLI", path);
if (typeof firstStatic === "string") store("event").set("command", firstStatic.replace(/\//gu, " ").trim());
const handlers = foundHandlers || this.opts?.onNotFound && [this.opts.onNotFound] || null;
if (handlers) try {
for (const handler of handlers) {
const response = await handler();
if (typeof response === "string") console.log(response);
else if (Array.isArray(response)) response.forEach((r) => {
console.log(typeof r === "string" ? r : JSON.stringify(r, null, " "));
});
else if (response instanceof Error) {
this.onError(response);
return response;
} else if (response) console.log(JSON.stringify(response, null, " "));
}
} catch (error) {
this.onError(error);
return error;
}
else {
this.onUnknownCommand(pathParams);
return /* @__PURE__ */ new Error("Unknown command");
}
});
}
onError(e) {
if (this.opts?.onError) this.opts.onError(e);
else {
this.error(e.message);
process.exit(1);
}
}
/**
* Triggers `unknown command` processing and callbacks
* @param pathParams `string[]` containing command
*/
onUnknownCommand(pathParams) {
const raiseError = () => {
this.error(`[0mUnknown command: ${pathParams.join(" ")}`);
process.exit(1);
};
if (this.opts?.onUnknownCommand) this.opts.onUnknownCommand(pathParams, raiseError);
else raiseError();
}
error(e) {
if (typeof e === "string") console.error(`[31mERROR: [0m${e}`);
else console.error(`[31mERROR: [0m${e.message}`);
}
};
/**
* Factory for WooksCli App
* @param opts TWooksCliOptions
* @param wooks Wooks | WooksAdapterBase
* @returns WooksCli
*/
function createCliApp(opts, wooks$1) {
return new WooksCli(opts, wooks$1);
}
//#endregion
//#region packages/event-cli/src/composables/options.ts
/**
* Get CLI Options
*
* @returns an object with CLI options
*/
function useCliOptions() {
const { store } = useCliContext();
const flags = store("flags");
if (!flags.value) {
const event = store("event");
flags.value = (0, minimist.default)(event.get("argv"), event.get("opts"));
}
return flags.value;
}
/**
* Getter for Cli Option value
*
* @param name name of the option
* @returns value of a CLI option
*/
function useCliOption(name) {
try {
const options = useCliHelp().getEntry().options || [];
const opt = options.find((o) => o.keys.includes(name));
if (opt) {
for (const key of opt.keys) if (useCliOptions()[key]) return useCliOptions()[key];
}
} catch (error) {}
return useCliOptions()[name];
}
//#endregion
//#region packages/event-cli/src/composables/cli-help.ts
/**
* ## useCliHelp
* ### Composable
* ```js
* // example of printing cli instructions
* const { print } = useCliHelp()
* // print with colors
* print(true)
* // print with no colors
* // print(false)
* ```
* @returns
*/
function useCliHelp() {
const event = useCliContext().store("event");
const getCliHelp = () => event.get("cliHelp");
const getEntry = () => getCliHelp().match(event.get("command")).main;
return {
getCliHelp,
getEntry,
render: (width, withColors) => getCliHelp().render(event.get("command"), width, withColors),
print: (withColors) => {
getCliHelp().print(event.get("command"), withColors);
}
};
}
/**
* ## useAutoHelp
* ### Composable
*
* Prints help if `--help` option provided.
*
* ```js
* // example of use: print help and exit
* app.cli('test', () => {
* useAutoHelp() && process.exit(0)
* return 'hit test command'
* })
*
* // add option -h to print help, no colors
* app.cli('test/nocolors', () => {
* useAutoHelp(['help', 'h'], false) && process.exit(0)
* return 'hit test nocolors command'
* })
* ```
* @param keys default `['help']` - list of options to trigger help render
* @param colors default `true`, prints with colors when true
* @returns true when --help was provided. Otherwise returns false
*/
function useAutoHelp(keys = ["help"], colors = true) {
for (const option of keys) if (useCliOption(option) === true) {
useCliHelp().print(colors);
return true;
}
}
/**
* ##useCommandLookupHelp
* ### Composable
*
* Tries to find valid command based on provided command.
*
* If manages to find a valid command, throws an error
* suggesting a list of valid commands
*
* Best to use in `onUnknownCommand` callback:
*
* ```js
* const app = createCliApp({
* onUnknownCommand: (path, raiseError) => {
* // will throw an error suggesting a list
* // of valid commands if could find some
* useCommandLookupHelp()
* // fallback to a regular error handler
* raiseError()
* },
* })
* ```
*
* @param lookupDepth depth of search in backwards
* @example
*
* For provided command `run test:drive dir`
* - lookup1: `run test:drive dir` (deep = 0)
* - lookup2: `run test:drive` (deep = 1)
* - lookup3: `run test` (deep = 2)
* - lookup4: `run` (deep = 3)
* ...
*/
function useCommandLookupHelp(lookupDepth = 3) {
const parts = useCliContext().store("event").get("pathParams")?.flatMap((p) => `${p} `.split(":").map((s, i) => i ? `:${s}` : s)) || [];
const cliHelp = useCliHelp().getCliHelp();
const cmd = cliHelp.getCliName();
let data;
for (let i = 0; i < Math.min(parts.length, lookupDepth + 1); i++) {
const pathParams = parts.slice(0, i ? -i : parts.length).join("").trim();
try {
data = cliHelp.match(pathParams);
break;
} catch (error) {
const variants = cliHelp.lookup(pathParams);
if (variants.length > 0) throw new Error(`Wrong command, did you mean:\n${variants.slice(0, 7).map((c) => ` $ ${cmd} ${c.main.command}`).join("\n")}`);
}
}
if (data) {
const { main, children } = data;
if (main.args && Object.keys(main.args).length > 0) throw new Error(`Arguments expected: ${Object.keys(main.args).map((l) => `<${l}>`).join(", ")}`);
else if (children?.length > 0) throw new Error(`Wrong command, did you mean:\n${children.slice(0, 7).map((c) => ` $ ${cmd} ${c.command}`).join("\n")}`);
}
}
//#endregion
exports.WooksCli = WooksCli;
exports.cliShortcuts = cliShortcuts;
exports.createCliApp = createCliApp;
exports.createCliContext = createCliContext;
exports.useAutoHelp = useAutoHelp;
exports.useCliContext = useCliContext;
exports.useCliHelp = useCliHelp;
exports.useCliOption = useCliOption;
exports.useCliOptions = useCliOptions;
exports.useCommandLookupHelp = useCommandLookupHelp;