commandstruct
Version:
Type safe and modular CLIs with Sade
682 lines (671 loc) • 19.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/arg.ts
var Arg = class {
constructor(_type) {
this._type = _type;
}
optional() {
this._type = "optional";
return this;
}
static toObject(arg2) {
return {
type: arg2._type
};
}
static toString(arg2, name) {
if (arg2._type === "required") return `<${name}>`;
else return `[${name}]`;
}
};
function arg() {
return new Arg("required");
}
// src/command.ts
import {
Hollywood
} from "hollywood-di";
// src/errors.ts
var CommandError = class extends Error {
constructor(reason, message) {
super(message);
this.reason = reason;
this.message = message;
}
};
// src/flag.ts
var Flag = class _Flag {
constructor(_desc, _char, _param, _preserveCase, _negate) {
this._desc = _desc;
this._char = _char;
this._param = _param;
this._preserveCase = _preserveCase;
this._negate = _negate;
}
char(char) {
this._char = char;
return this;
}
requiredParam(type) {
this._param = { _type: "required", type };
return this;
}
optionalParam(type, defaultValue) {
this._param = { _type: "optional", type, defaultValue };
return this;
}
withNegated(description) {
this._negate = description;
return this;
}
preserveCase() {
this._preserveCase = true;
return this;
}
static toObject(flag2) {
return {
desc: flag2._desc,
char: flag2._char,
param: flag2._param,
negate: flag2._negate,
preserveCase: flag2._preserveCase
};
}
static toString(flag2, name) {
let str = `--${flag2._preserveCase ? name : _Flag.toKebabCase(name)}`;
if (flag2._char) str = `-${flag2._char}, ${str}`;
return str;
}
static toNegatedString(flag2, name) {
let str = `--no-${flag2._preserveCase ? name : _Flag.toKebabCase(name)}`;
return str;
}
static isNegated(str) {
return str.startsWith("no-");
}
static toKebabCase(str) {
return str.split("").map((char, index) => {
if (index === 0) return char.toLowerCase();
return char.toLowerCase() !== char ? `-${char.toLowerCase()}` : char;
}).join("");
}
static toCamelCase(str) {
return str.replace(/-./g, (x) => x[1].toUpperCase());
}
};
function flag(description) {
return new Flag(description, void 0, void 0, false, void 0);
}
// src/utils.ts
function registerFlags(program, flags) {
for (const [name, flag2] of Object.entries(flags)) {
const flagObj = Flag.toObject(flag2);
if (flagObj.char !== void 0 && flagObj.char.length !== 1) {
throw new CommandError(
"invalid_flag",
`option ${"`" + Flag.toString(flag2, name) + "`"} char must be 1 character`
);
}
if (flagObj.param && flagObj.negate !== void 0) {
throw new CommandError(
"invalid_flag",
`negated option ${name} ${"`" + Flag.toString(flag2, name) + "`"} cannot have param`
);
}
const key = flagObj.preserveCase ? name : Flag.toKebabCase(name);
if (Flag.isNegated(key)) {
if (flagObj.param) {
throw new CommandError(
"invalid_flag",
`negated option ${name} ${"`" + Flag.toString(flag2, name) + "`"} cannot have param`
);
}
if (flagObj.char !== void 0) {
throw new CommandError(
"invalid_flag",
`negated option ${name} ${"`" + Flag.toString(flag2, name) + "`"} cannot have char`
);
}
if (flagObj.negate !== void 0) {
throw new CommandError(
"invalid_flag",
`negated option ${name} ${"`" + Flag.toString(flag2, name) + "`"} is already negated`
);
}
const existingKey = flags[key.slice(3)];
if (existingKey && Flag.toObject(existingKey).param) {
throw new CommandError(
"invalid_flag",
`negated option ${name} ${"`" + Flag.toString(flag2, name) + "`"} can only be used with boolean flags`
);
}
}
program.option(
Flag.toString(flag2, name),
flagObj.desc,
flagObj.negate && true
);
if (flagObj.negate !== void 0) {
program.option(Flag.toNegatedString(flag2, name), flagObj.negate);
}
}
}
function commandUsage(baseCmd, cmd, args) {
let usage = cmd;
if (baseCmd) usage = baseCmd + " " + cmd;
let hasOptional = false;
for (const [name, arg2] of Object.entries(args)) {
const argObj = Arg.toObject(arg2);
if (argObj.type === "required" && hasOptional) {
throw new CommandError(
"invalid_arg",
`required positional argument ${"`" + Arg.toString(arg2, name) + "`"} cannot appear after an optional argument`
);
}
hasOptional || (hasOptional = argObj.type === "optional");
usage += " " + Arg.toString(arg2, name);
}
return usage;
}
function commandContext(args, flags, fnArgs) {
const _args = Object.keys(args).reduce((acc, name, index) => {
acc[name] = fnArgs[index];
return acc;
}, {});
const _a = fnArgs[fnArgs.length - 1], { _ } = _a, opts = __objRest(_a, ["_"]);
for (const [name, flag2] of Object.entries(flags)) {
const flagObj = Flag.toObject(flag2);
const key = flagObj.preserveCase ? name : Flag.toKebabCase(name);
const value = opts[key];
if (!flagObj.param) {
if (flagObj.negate || Flag.isNegated(key)) {
const negatedKey = flagObj.negate ? `no-${key}` : key;
delete opts[negatedKey];
}
if (value === void 0) {
if (Flag.isNegated(key)) {
const negatedKey = key.slice(3);
if (opts[negatedKey] === void 0) {
opts[negatedKey] = true;
}
continue;
}
opts[key] = false;
if (flagObj.char) opts[flagObj.char] = false;
} else if (typeof value !== "boolean") {
opts[key] = true;
if (flagObj.char) opts[flagObj.char] = true;
}
continue;
}
if (value === void 0) {
if (flagObj.param._type === "optional") {
if (flagObj.param.defaultValue !== void 0) {
opts[key] = flagObj.param.defaultValue;
if (flagObj.char) opts[flagObj.char] = flagObj.param.defaultValue;
}
continue;
}
throw new CommandError(
"invalid_flag",
`option ${"`" + Flag.toString(flag2, name) + "`"} value is missing`
);
}
if (flagObj.param.type === "string" && typeof value !== "string") {
if (typeof value !== "object") {
if (value === true || value === void 0)
throw new CommandError(
"invalid_flag",
`option ${"`" + Flag.toString(flag2, name) + "`"} value is missing`
);
const parsedValue = String(value);
opts[key] = parsedValue;
if (flagObj.char) opts[flagObj.char] = parsedValue;
} else if (Array.isArray(value)) {
const val = value[value.length - 1];
const parsedValue = val === true ? "" : String(val);
opts[key] = parsedValue;
if (flagObj.char) opts[flagObj.char] = parsedValue;
} else {
throw new CommandError(
"invalid_flag",
`option ${"`" + Flag.toString(flag2, name) + "`"} value is not a string`
);
}
}
if (flagObj.param.type === "number" && typeof value !== "number") {
const parsedValue = Number(value);
if (typeof value !== "string" || Number.isNaN(parsedValue)) {
throw new CommandError(
"invalid_flag",
`option ${"`" + Flag.toString(flag2, name) + "`"} value is not a number`
);
}
opts[key] = parsedValue;
if (flagObj.char) opts[flagObj.char] = parsedValue;
}
if (flagObj.param.type === "array") {
if (Array.isArray(value)) {
const parsedValue = value.map(
(val) => val === true ? "" : String(val)
);
opts[key] = parsedValue;
if (flagObj.char) opts[flagObj.char] = parsedValue;
} else {
if (typeof value === "object" || value === true)
throw new CommandError(
"invalid_flag",
`option ${"`" + Flag.toString(flag2, name) + "`"} value is not an array`
);
const parsedValue = [String(value)];
opts[key] = parsedValue;
if (flagObj.char) opts[flagObj.char] = parsedValue;
}
}
}
return {
args: _args,
flags: opts,
restArgs: _
};
}
// src/command.ts
var Command = class {
constructor(options) {
this.options = options;
}
command(options) {
var _a;
const {
program,
programFlags,
baseCmd,
defaultCmd,
container: parentContainer
} = options;
const command = program.command(
commandUsage(baseCmd, this.options.name, this.options.args),
this.options.description,
{
default: defaultCmd && defaultCmd === this
}
);
if (this.options.aliases.length) command.alias(...this.options.aliases);
for (const example of this.options.examples) command.example(example);
registerFlags(program, this.options.flags);
let container = parentContainer;
const subcommandTasks = [];
for (const [tokens, subcommands] of this.options.subcommands) {
let subContainer = container;
if (tokens && subContainer)
subContainer = Hollywood.createWithParent(
subContainer,
tokens.tokens,
tokens.options
);
else if (tokens)
subContainer = Hollywood.create(tokens.tokens, tokens.options);
container = subContainer;
for (const subcommand of subcommands) {
subcommandTasks.push(() => {
subcommand.command({
program,
programFlags,
baseCmd: `${baseCmd ? baseCmd + " " + this.options.name : this.options.name}`,
defaultCmd: defaultCmd === this ? void 0 : defaultCmd,
container: subContainer
});
});
}
}
const instances = (_a = container == null ? void 0 : container.instances) != null ? _a : {};
command.action((...fnArgs) => {
const _a2 = commandContext(
this.options.args,
__spreadValues(__spreadValues({}, programFlags), this.options.flags),
fnArgs
), { flags } = _a2, context = __objRest(_a2, ["flags"]);
return this.options.action(
__spreadProps(__spreadValues({}, context), {
flags
}),
instances
);
});
for (const task of subcommandTasks) task();
}
};
var CommandBuilder = class {
constructor(name) {
this.options = {
name,
description: void 0,
aliases: [],
examples: [],
args: {},
flags: {},
subcommands: []
};
}
describe(description) {
this.options.description = description;
return this;
}
alias(...aliases) {
this.options.aliases.push(...aliases);
return this;
}
example(example) {
this.options.examples.push(example);
return this;
}
args(args) {
this.options.args = args;
return this;
}
flags(flags) {
this.options.flags = flags;
return this;
}
useFlags() {
return this;
}
use() {
return this;
}
provide(tokens, options) {
this.options.subcommands.push([{ tokens, options }, []]);
return this;
}
subcommands(...commands) {
if (!this.options.subcommands.length)
this.options.subcommands.push([void 0, []]);
const [, subcommands] = this.options.subcommands[this.options.subcommands.length - 1];
subcommands.push(...commands);
return this;
}
action(fn) {
return new Command(__spreadProps(__spreadValues({}, this.options), {
action: fn
}));
}
};
function createCommand(name) {
return new CommandBuilder(name);
}
// src/program.ts
import {
Hollywood as Hollywood2
} from "hollywood-di";
import sade from "sade";
// src/run.ts
function run(_0, _1) {
return __async(this, arguments, function* (cli, options, argv = process.argv) {
try {
const output = cli.parse(argv, {
lazy: true,
unknown: (options == null ? void 0 : options.errorOnUnknown) === true ? (
// will execute default unknown callback if true
() => {
}
) : (
// executes provided callback or allows unknown if undefined
(options == null ? void 0 : options.errorOnUnknown) === false ? void 0 : options == null ? void 0 : options.errorOnUnknown
)
});
if (!output) return;
const res = yield output.handler.apply(null, output.args);
return res;
} catch (err) {
if (process.exitCode === void 0) process.exitCode = 1;
if (options == null ? void 0 : options.onError) options.onError(err);
else if (err instanceof CommandError) console.error("error:", err.message);
else throw err;
}
});
}
// src/program.ts
var Program = class {
constructor(options) {
this.options = options;
}
program() {
const program = sade(this.options.name);
if (this.options.version) program.version(this.options.version);
if (this.options.description) program.describe(this.options.description);
for (const example of this.options.examples) program.example(example);
registerFlags(program, this.options.flags);
for (const [container, commands] of this.options.commands) {
for (const command of commands) {
command.command({
program,
programFlags: this.options.flags,
defaultCmd: this.options.default,
container
});
}
}
return program;
}
run(options, argv) {
return run(this.program(), options, argv);
}
};
var ProgramBuilder = class {
constructor(name, container) {
this.options = {
name,
version: void 0,
description: void 0,
examples: [],
flags: {},
container,
commands: [],
default: void 0
};
}
version(version) {
this.options.version = version;
return this;
}
describe(description) {
this.options.description = description;
return this;
}
example(example) {
this.options.examples.push(example);
return this;
}
flags(flags) {
this.options.flags = flags;
return this;
}
provide(tokens, options) {
let parent = this.options.container;
if (parent)
this.options.container = Hollywood2.createWithParent(
parent,
tokens,
options
);
else
this.options.container = Hollywood2.create(
tokens,
options
);
this.options.commands.push([
this.options.container,
[]
]);
return this;
}
commands(...commands) {
if (!this.options.commands.length)
this.options.commands.push([
this.options.container,
[]
]);
const [, subcommands] = this.options.commands[this.options.commands.length - 1];
subcommands.push(...commands);
return this;
}
default(command) {
this.options.default = command;
return this;
}
build() {
return new Program(this.options);
}
};
function createProgram(name, container) {
return new ProgramBuilder(name, container);
}
// src/single.ts
import {
Hollywood as Hollywood3
} from "hollywood-di";
import sade2 from "sade";
var SingleProgram = class {
constructor(options) {
this.options = options;
}
program() {
var _a, _b;
const program = sade2(
commandUsage(void 0, this.options.name, this.options.args),
true
);
if (this.options.version) program.version(this.options.version);
if (this.options.description) program.describe(this.options.description);
for (const example of this.options.examples) program.example(example);
registerFlags(program, this.options.flags);
const instances = (_b = (_a = this.options.container) == null ? void 0 : _a.instances) != null ? _b : {};
return program.action((...fnArgs) => {
const ctx = commandContext(this.options.args, this.options.flags, fnArgs);
return this.options.action(ctx, instances);
});
}
run(options, argv) {
return run(this.program(), options, argv);
}
};
var SingleProgramBuilder = class {
constructor(name, container) {
this.options = {
name,
version: void 0,
description: void 0,
examples: [],
args: {},
flags: {},
container
};
}
version(version) {
this.options.version = version;
return this;
}
describe(description) {
this.options.description = description;
return this;
}
example(example) {
this.options.examples.push(example);
return this;
}
args(args) {
this.options.args = args;
return this;
}
flags(flags) {
this.options.flags = flags;
return this;
}
provide(tokens, options) {
let parent = this.options.container;
if (parent)
this.options.container = Hollywood3.createWithParent(
parent,
tokens,
options
);
else
this.options.container = Hollywood3.create(
tokens,
options
);
return this;
}
action(fn) {
return new SingleProgram(__spreadProps(__spreadValues({}, this.options), {
action: fn
}));
}
};
function createSingleProgram(name, container) {
return new SingleProgramBuilder(name, container);
}
export {
Arg,
Command,
CommandError,
Flag,
Program,
SingleProgram,
arg,
createCommand,
createProgram,
createSingleProgram,
flag,
run
};