commandstruct
Version:
Type safe and modular CLIs with Sade
721 lines (709 loc) • 21.2 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
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 __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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/index.ts
var index_exports = {};
__export(index_exports, {
Arg: () => Arg,
Command: () => Command,
CommandError: () => CommandError,
Flag: () => Flag,
Program: () => Program,
SingleProgram: () => SingleProgram,
arg: () => arg,
createCommand: () => createCommand,
createProgram: () => createProgram,
createSingleProgram: () => createSingleProgram,
flag: () => flag,
run: () => run
});
module.exports = __toCommonJS(index_exports);
// 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
var import_hollywood_di = require("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 = import_hollywood_di.Hollywood.createWithParent(
subContainer,
tokens.tokens,
tokens.options
);
else if (tokens)
subContainer = import_hollywood_di.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
var import_hollywood_di2 = require("hollywood-di");
var import_sade = __toESM(require("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 = (0, import_sade.default)(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 = import_hollywood_di2.Hollywood.createWithParent(
parent,
tokens,
options
);
else
this.options.container = import_hollywood_di2.Hollywood.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
var import_hollywood_di3 = require("hollywood-di");
var import_sade2 = __toESM(require("sade"));
var SingleProgram = class {
constructor(options) {
this.options = options;
}
program() {
var _a, _b;
const program = (0, import_sade2.default)(
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 = import_hollywood_di3.Hollywood.createWithParent(
parent,
tokens,
options
);
else
this.options.container = import_hollywood_di3.Hollywood.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);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Arg,
Command,
CommandError,
Flag,
Program,
SingleProgram,
arg,
createCommand,
createProgram,
createSingleProgram,
flag,
run
});